浅夏星空 发表于 2026-7-2 09:20:59

<p>好的,基于已获取的手册数据和论坛信息,我来为您编写一个完整的 STC32G12K128 演示程序。</p>
<hr />
<h2>STC32G12K128 三相互补PWM + ADC电流采样演示程序</h2>
<p>以下程序实现:</p>
<ul>
<li><strong>PWM1P/PWM1N</strong> → P2.0/P2.1(U相)</li>
<li><strong>PWM2P/PWM2N</strong> → P2.2/P2.3(V相)</li>
<li><strong>PWM3P/PWM3N</strong> → P2.4/P2.5(W相)</li>
<li>三相互补输出,带死区</li>
<li>计数器归零(更新事件)触发 ADC0、ADC1、ADC3 采样三相电流</li>
</ul>
<pre><code class="language-c">/******************************************************************************
* 文件名: main.c
* MCU   : STC32G12K128
* 功能: 三相互补PWM输出 + 更新事件触发ADC三相电流采样
*          PWM1P/PWM1N -&gt; P2.0/P2.1 (U相)
*          PWM2P/PWM2N -&gt; P2.2/P2.3 (V相)
*          PWM3P/PWM3N -&gt; P2.4/P2.5 (W相)
*          ADC0(P1.0) -&gt; U相电流
*          ADC1(P1.1) -&gt; V相电流
*          ADC3(P1.3) -&gt; W相电流
*          更新事件(计数器归零)触发ADC同步采样
******************************************************************************/

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

#define MAIN_Fosc       24000000UL      // 系统主频 24MHz
#define PWM_FREQ      20000UL         // PWM频率 20kHz
#define PWM_PERIOD      (MAIN_Fosc / PWM_FREQ)// PWM周期计数值
#define DEAD_TIME      100             // 死区时间 (约 100个时钟 ≈ 4.17us @24MHz)

// ADC结果存储
volatile unsigned int adc0_result;
volatile unsigned int adc1_result;
volatile unsigned int adc3_result;
volatile unsigned char adc_flag;      // ADC转换完成标志

// 函数声明
void delay_ms(unsigned int ms);
void GPIO_config(void);
void PWMA_config(void);
void ADC_config(void);

/****************************** 主函数 ***************************************/
void main(void)
{
    EAXFR = 1;          // 使能访问XFR
    CKCON = 0x00;       // 设置外部数据总线速度为最快
    WTST = 0x00;      // 设置程序代码等待参数,赋值为0可将CPU执行程序的速度设置为最快

    GPIO_config();      // 配置GPIO
    PWMA_config();      // 配置高级PWM
    ADC_config();       // 配置ADC

    EA = 1;             // 开启总中断

    while(1)
    {
      // 主循环可做其他任务
      // 当 adc_flag == 1 时,表示一次三相电流采样完成
      if(adc_flag)
      {
            adc_flag = 0;
            // 在此处处理三相电流数据:adc0_result, adc1_result, adc3_result
            // 例如通过串口发送等
      }
    }
}

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

/****************************** GPIO配置 *************************************/
void GPIO_config(void)
{
    // P2.0~P2.5 设置为推挽输出(PWM输出脚)
    P2M0 = 0x3F;      // P2.0~P2.5 推挽输出 (PnM0=1, PnM1=0)
    P2M1 = 0x00;

    // P1.0, P1.1, P1.3 设置为高阻输入(ADC输入通道)
    P1M0 = 0x00;
    P1M1 = 0x0B;      // P1.0(bit0)=1, P1.1(bit1)=1, P1.3(bit3)=1 =&gt; 0x0B

    // 其余IO口设为准双向
    P0M0 = 0x00; P0M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
    P4M0 = 0x00; P4M1 = 0x00;
    P5M0 = 0x00; P5M1 = 0x00;
    P6M0 = 0x00; P6M1 = 0x00;
    P7M0 = 0x00; P7M1 = 0x00;
}

/****************************** 高级PWM配置 **********************************/
void PWMA_config(void)
{
    // 1. 功能脚切换:将PWM1、PWM2、PWM3映射到P2口
    //    C1PS=01 -&gt; PWM1P=P2.0, PWM1N=P2.1
    //    C2PS=01 -&gt; PWM2P=P2.2, PWM2N=P2.3
    //    C3PS=01 -&gt; PWM3P=P2.4, PWM3N=P2.5
    PWMA_PS = 0x55;   // 01 01 01 01 (C4PS=01, C3PS=01, C2PS=01, C1PS=01)

    // 2. 配置PWM时钟和周期
    PWMA_PSCRH = 0x00;// 预分频 = 0,即 PWM时钟 = 系统时钟 = 24MHz
    PWMA_PSCRL = 0x00;

    // 周期 = PWM_PERIOD
    PWMA_ARRH = (unsigned char)(PWM_PERIOD &gt;&gt; 8);
    PWMA_ARRL = (unsigned char)(PWM_PERIOD);

    // 3. 配置通道1 (PWM1) - U相 - 占空比50%
    PWMA_CCMR1 = 0x60;// OC1M=110 (PWM模式1), OC1PE=1(预装载使能)
    PWMA_CCR1H = (unsigned char)((PWM_PERIOD / 2) &gt;&gt; 8);   // 占空比50%
    PWMA_CCR1L = (unsigned char)(PWM_PERIOD / 2);

    // 4. 配置通道2 (PWM2) - V相 - 占空比40%
    PWMA_CCMR2 = 0x60;// OC2M=110 (PWM模式1), OC2PE=1
    PWMA_CCR2H = (unsigned char)((PWM_PERIOD * 40 / 100) &gt;&gt; 8);// 占空比40%
    PWMA_CCR2L = (unsigned char)(PWM_PERIOD * 40 / 100);

    // 5. 配置通道3 (PWM3) - W相 - 占空比30%
    PWMA_CCMR3 = 0x60;// OC3M=110 (PWM模式1), OC3PE=1
    PWMA_CCR3H = (unsigned char)((PWM_PERIOD * 30 / 100) &gt;&gt; 8);// 占空比30%
    PWMA_CCR3L = (unsigned char)(PWM_PERIOD * 30 / 100);

    // 6. 配置捕获比较使能寄存器 - 使能互补输出
    //    CC1E=1(使能PWM1P), CC1NE=1(使能PWM1N)
    //    CC2E=1(使能PWM2P), CC2NE=1(使能PWM2N)
    //    CC3E=1(使能PWM3P), CC3NE=1(使能PWM3N)
    //    极性:CCxP=0(高电平有效), CCxNP=0(高电平有效)
    PWMA_CCER1 = 0x55;// 0101 0101: CC1E=1,CC1NE=1,CC2E=1,CC2NE=1
    PWMA_CCER2 = 0x05;// 0000 0101: CC3E=1,CC3NE=1

    // 7. 配置死区时间
    PWMA_DTR = DEAD_TIME;   // 死区时间 = DEAD_TIME个PWM时钟周期

    // 8. 配置刹车和主输出使能
    //    MOE=1(主输出使能), AOE=1(自动输出使能)
    PWMA_BKR = 0x80;    // MOE=1

    // 9. 使能PWM输出引脚
    //    ENO1P=1, ENO1N=1, ENO2P=1, ENO2N=1, ENO3P=1, ENO3N=1
    PWMA_ENO = 0x3F;    // 0011 1111

    // 10. 配置CR2 - 选择更新事件作为TRGO触发输出
    //   MMS=010 (更新事件作为TRGO)
    //   用于触发ADC转换(计数器归零时触发)
    PWMA_CR2 = 0x20;    // MMS=010: 更新事件作为TRGO输出

    // 11. 使能更新中断(可选,用于调试)
    // PWMA_IER = 0x01; // UIE=1 使能更新中断

    // 12. 启动PWM计数器
    //   CEN=1(使能计数器), ARPE=1(自动重装载预装载使能)
    PWMA_CR1 = 0x81;    // CEN=1, ARPE=1
}

/****************************** ADC配置 **************************************/
void ADC_config(void)
{
    // 1. 配置ADC时序
    //    CSSETUP=0(1个时钟), CSHOLD=01(2个时钟)
    //    SMPDUTY=11111(32个时钟)
    ADCTIM = 0x3F;      // 0011 1111: 采样时间最大化,保证精度

    // 2. 配置ADC时钟和结果格式
    //    SPEED=1111 -&gt; FADC = SYSclk/2/16 = 24MHz/2/16 = 750kHz
    //    RESFMT=0 -&gt; 左对齐 (ADC_RES高8位, ADC_RESL低4位)
    ADCCFG = 0x0F;      // SPEED=1111, RESFMT=0

    // 3. 打开ADC电源,使能PWM触发ADC,选择ADC0通道(先选一个)
    //    ADC_POWER=1, ADC_EPWMT=1, ADC_CHS=0000(ADC0)
    ADC_CONTR = 0x80;   // 1000 0000: 打开ADC电源,先不使能PWM触发
    delay_ms(2);      // 等待ADC电源稳定(至少1ms)

    // 4. 使能ADC中断
    EADC = 1;         // 使能ADC中断
}

/****************************** ADC中断服务函数 ******************************/
void ADC_Isr(void) interrupt 5
{
    unsigned char ch;
    unsigned int adc_val;

    ADC_FLAG = 0;       // 清中断标志

    // 读取当前ADC通道的转换结果
    adc_val = ((unsigned int)ADC_RES &lt;&lt; 8) | ADC_RESL;

    // 根据当前通道保存结果
    ch = ADC_CONTR &amp; 0x0F;// 获取当前ADC通道号

    switch(ch)
    {
      case 0:   // ADC0 - U相电流
            adc0_result = adc_val;
            // 切换到下一个通道 ADC1
            ADC_CONTR = 0x88;   // ADC_POWER=1, ADC_EPWMT=1, ADC_CHS=0001(ADC1)
            break;

      case 1:   // ADC1 - V相电流
            adc1_result = adc_val;
            // 切换到下一个通道 ADC3
            ADC_CONTR = 0x8B;   // ADC_POWER=1, ADC_EPWMT=1, ADC_CHS=0011(ADC3)
            break;

      case 3:   // ADC3 - W相电流
            adc3_result = adc_val;
            adc_flag = 1;       // 三相全部采集完成
            // 切换回 ADC0,等待下一个PWM周期触发
            ADC_CONTR = 0x80;   // ADC_POWER=1, ADC_EPWMT=1, ADC_CHS=0000(ADC0)
            break;

      default:
            ADC_CONTR = 0x80;   // 异常情况,复位到ADC0
            break;
    }
}

/****************************** PWM更新中断(可选)***************************/
// 如果使能了PWM更新中断,可在此处理
// void PWMA_Isr(void) interrupt 22
// {
//   PWMA_SR1 = 0x00;// 清中断标志
// }
</code></pre>
<hr />
<h3>程序说明</h3>
<h4>1. PWM 配置要点(参考手册第26章&quot;16位高级PWM定时器&quot;)</h4>
<table>
<thead>
<tr>
<th>配置项</th>
<th>寄存器</th>
<th>值</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>引脚映射</td>
<td><code>PWMA_PS</code></td>
<td><code>0x55</code></td>
<td>C1PS=01→P2.0/P2.1, C2PS=01→P2.2/P2.3, C3PS=01→P2.4/P2.5</td>
</tr>
<tr>
<td>PWM模式</td>
<td><code>PWMA_CCMR1/2/3</code></td>
<td><code>0x60</code></td>
<td>OCxM=110(PWM模式1),向上计数,OCxREF高电平有效</td>
</tr>
<tr>
<td>互补使能</td>
<td><code>PWMA_CCER1</code></td>
<td><code>0x55</code></td>
<td>CC1E+CC1NE, CC2E+CC2NE</td>
</tr>
<tr>
<td></td>
<td><code>PWMA_CCER2</code></td>
<td><code>0x05</code></td>
<td>CC3E+CC3NE</td>
</tr>
<tr>
<td>死区</td>
<td><code>PWMA_DTR</code></td>
<td><code>100</code></td>
<td>约4.17μs @24MHz</td>
</tr>
<tr>
<td>主输出</td>
<td><code>PWMA_BKR</code></td>
<td><code>0x80</code></td>
<td>MOE=1</td>
</tr>
<tr>
<td>输出使能</td>
<td><code>PWMA_ENO</code></td>
<td><code>0x3F</code></td>
<td>ENO1P~ENO3N全部使能</td>
</tr>
<tr>
<td><strong>TRGO触发</strong></td>
<td><code>PWMA_CR2</code></td>
<td><code>0x20</code></td>
<td><strong>MMS=010,更新事件作为TRGO</strong></td>
</tr>
<tr>
<td>启动</td>
<td><code>PWMA_CR1</code></td>
<td><code>0x81</code></td>
<td>CEN=1, ARPE=1</td>
</tr>
</tbody>
</table>
<h4>2. ADC 配置要点(参考手册第22章&quot;ADC模数转换&quot;)</h4>
<ul>
<li><strong>ADC_EPWMT=1</strong>(<code>ADC_CONTR.4</code>):使能PWM实时触发ADC功能</li>
<li><strong>PWM的TRGO(更新事件)</strong> 连接到ADC的触发输入,当PWM计数器归零(更新事件)时,自动启动ADC转换</li>
<li>三个ADC通道(ADC0/ADC1/ADC3)通过<strong>中断中切换通道</strong>的方式依次采样</li>
</ul>
<h4>3. 采样时序</h4>
<pre><code>PWM计数器:▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁
            ↑                                     ↑
         计数器=0 (更新事件)                  计数器=ARR
         TRGO触发ADC                           (下一个周期)
            │
            ├─ 第1次触发 → ADC0采样(U相电流)
            ├─ 第2次触发 → ADC1采样(V相电流)
            └─ 第3次触发 → ADC3采样(W相电流)
</code></pre>
<blockquote>
<p><strong>注意</strong>:由于三个ADC通道共用1个ADC模块,本程序采用&quot;每次PWM触发采一个通道,中断中切换&quot;的方式,三个PWM周期完成一轮三相采样。如需在一个PWM周期内同时采样三相,STC32G12K128只有一个ADC模块,无法真正同时采样,可通过外部采样保持电路或使用更高速的ADC轮询方式近似实现。</p>
</blockquote>
<h4>4. 硬件连接建议</h4>
<table>
<thead>
<tr>
<th>信号</th>
<th>MCU引脚</th>
<th>连接</th>
</tr>
</thead>
<tbody>
<tr>
<td>U相上桥 PWM1P</td>
<td>P2.0</td>
<td>→ 驱动芯片U相上桥</td>
</tr>
<tr>
<td>U相下桥 PWM1N</td>
<td>P2.1</td>
<td>→ 驱动芯片U相下桥</td>
</tr>
<tr>
<td>V相上桥 PWM2P</td>
<td>P2.2</td>
<td>→ 驱动芯片V相上桥</td>
</tr>
<tr>
<td>V相下桥 PWM2N</td>
<td>P2.3</td>
<td>→ 驱动芯片V相下桥</td>
</tr>
<tr>
<td>W相上桥 PWM3P</td>
<td>P2.4</td>
<td>→ 驱动芯片W相上桥</td>
</tr>
<tr>
<td>W相下桥 PWM3N</td>
<td>P2.5</td>
<td>→ 驱动芯片W相下桥</td>
</tr>
<tr>
<td>U相电流采样</td>
<td>P1.0 (ADC0)</td>
<td>→ 电流检测运放输出</td>
</tr>
<tr>
<td>V相电流采样</td>
<td>P1.1 (ADC1)</td>
<td>→ 电流检测运放输出</td>
</tr>
<tr>
<td>W相电流采样</td>
<td>P1.3 (ADC3)</td>
<td>→ 电流检测运放输出</td>
</tr>
</tbody>
</table>
<h4>5. 使用说明</h4>
<ol>
<li>在 Keil 中创建项目,选择 <strong>STC32G12K128</strong> 型号</li>
<li>添加 <code>stc32g.h</code> 头文件(可从 STC-ISP 软件中获取)</li>
<li>编译下载到芯片</li>
<li>用示波器观察 P2.0~P2.5 可看到三相互补PWM波形</li>
<li>ADC结果存储在 <code>adc0_result</code>、<code>adc1_result</code>、<code>adc3_result</code> 中,在 <code>adc_flag=1</code> 时读取</li>
</ol>
<p>如需调整占空比,修改 <code>PWMA_CCR1/2/3</code> 的赋值即可;如需调整PWM频率,修改 <code>PWM_PERIOD</code> 宏定义。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC32G12K128%20PWM%E4%BA%92%E8%A1%A5%E8%BE%93%E5%87%BA%20%E4%B8%89%E7%9B%B8PWM%20ADC%E8%A7%A6%E5%8F%91%E9%87%87%E6%A0%B7&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC32G12K128 PWM互补输出 三相PWM ADC触发采样&quot;</a></p>
<p>⏱️ 查找用时 31.5s | 总回答用时 66.5s</p>

32G12K128 发表于 2026-7-2 09:21:11

USB不停电下载的几种方式推荐?
生成对应的参考程序?

32G12K128 发表于 2026-7-2 09:21:45

<p>好的,信息已经足够。下面为您详细解答。</p>
<hr />
<h2>STC单片机USB不停电下载的几种方式</h2>
<p>根据STC官方手册,USB不停电下载主要有以下 <strong>3种推荐方式</strong>:</p>
<hr />
<h3>方式一:USB库函数方式(最推荐,最简单)</h3>
<p><strong>适用芯片</strong>:支持USB的STC系列(如STC8H8K64U、STC32G12K128、Ai8051U、Ai8H等)</p>
<p><strong>原理</strong>:STC官方USB库函数中已内置了不停电自动ISP下载命令 <code>&quot;@STCISP#&quot;</code>,用户只需在程序中调用USB库,并在ISP下载软件中做相应设置即可。</p>
<p><strong>操作步骤</strong>:</p>
<ol>
<li>在用户程序中调用STC官方USB库函数(USB-CDC虚拟串口或HID)</li>
<li>在ISP下载软件(如AIapp-ISP)中,找到 <strong>&quot;收到用户命令后复位到ISP监控程序区&quot;</strong> 选项</li>
<li>设置自定义命令为 <code>@STCISP#</code></li>
<li>勾选 <strong>&quot;每次下载前都先发送自定义命令&quot;</strong></li>
</ol>
<p><strong>参考代码(C语言,使用USB-CDC库)</strong>:</p>
<pre><code class="language-c">// 使用STC官方USB库,只需在主程序中初始化USB即可
// USB库内部已处理 @STCISP# 命令的检测

#include &quot;stc8h.h&quot;
#include &quot;usb.h&quot;   // STC官方USB库头文件

void main()
{
    P_SW2 |= 0x80;// 使能访问XFR

    // USB初始化
    usb_init();

    EA = 1;// 开总中断

    while(1)
    {
      // 用户应用程序代码
    }
}
</code></pre>
<blockquote>
<p><strong>说明</strong>:USB库函数方式最为简单,用户无需自行编写命令检测代码,库函数已自动处理。当PC端ISP软件发送 <code>@STCISP#</code> 命令时,芯片自动复位到ISP区域进行下载。</p>
</blockquote>
<hr />
<h3>方式二:串口自定义命令方式(通用性强)</h3>
<p><strong>适用芯片</strong>:所有带串口的STC单片机</p>
<p><strong>原理</strong>:用户在应用程序中自行编写串口接收检测代码,当检测到PC端发送的特定自定义命令序列后,执行 <code>IAP_CONTR = 0x60;</code> 使MCU复位到ISP系统区,实现不停电下载。</p>
<p><strong>操作步骤</strong>:</p>
<ol>
<li>在用户程序中编写串口中断服务程序,检测自定义命令</li>
<li>检测到命令后执行 <code>IAP_CONTR = 0x60;</code>(C语言)或 <code>MOV IAP_CONTR,#60H</code>(汇编)</li>
<li>在ISP下载软件中设置相同的自定义命令(波特率、校验位、数据序列)</li>
</ol>
<p><strong>参考代码(C语言)</strong>:</p>
<pre><code class="language-c">// 串口自定义命令方式实现不停电下载
// 测试工作频率:11.0592MHz

#include &quot;stc8h.h&quot;

#define FOSC 11059200L
#define BAUD (65536 - (FOSC/115200+2)/4)

// 自定义命令序列(需与ISP软件设置一致)
code unsigned char CustomCmd[] = {0x12, 0x34, 0x56, 0xAB, 0xCD, 0xEF, 0x12};
unsigned char cmd_index = 0;

void uart_isr() interrupt 4
{
    if(RI)
    {
      RI = 0;
      
      // 检测自定义命令序列
      if(SBUF == CustomCmd)
      {
            cmd_index++;
            if(cmd_index &gt;= sizeof(CustomCmd))
            {
                // 检测到完整自定义命令,复位到ISP系统区
                IAP_CONTR = 0x60;   // 不停电下载!复位到ISP区域
                while(1);         // 等待复位
            }
      }
      else
      {
            cmd_index = 0;          // 匹配失败,重新开始
      }
    }
    if(TI)
    {
      TI = 0;
    }
}

void main()
{
    P_SW2 |= 0x80;

    // 初始化串口1,波特率115200
    SCON = 0x50;      // 8位可变波特率,无校验
    AUXR = 0x40;      // 定时器1为1T模式
    TMOD = 0x00;      // 定时器1模式0(16位自动重装载)
    TH1 = BAUD &gt;&gt; 8;
    TL1 = BAUD;
    TR1 = 1;            // 启动定时器1

    ES = 1;             // 使能串口中断
    EA = 1;             // 开总中断

    while(1)
    {
      // 用户应用程序
    }
}
</code></pre>
<p><strong>参考代码(汇编)</strong>:</p>
<pre><code class="language-assembly">; 串口自定义命令方式实现不停电下载(汇编版本)
; 测试工作频率:11.0592MHz

UARTBAUD    EQU   0FFE8H      ; 波特率重载值 (65536-11059200/4/115200)
AUXR      DATA    08EH
CNTCMD      DATA    60H         ; 命令匹配计数器

            ORG   0000H
            LJMP    START
            ORG   0023H
            LJMP    UART_ISR

UART_ISR:
            PUSH    ACC
            PUSH    PSW
            JNB   RI, UART_EXIT
            CLR   RI
            MOV   A, SBUF
            ; 这里添加自定义命令序列检测逻辑
            ; 检测到完整命令后执行:
            ; MOV IAP_CONTR, #60H
UART_EXIT:
            POP   PSW
            POP   ACC
            RETI

START:
            MOV   SP, #7FH
            MOV   SCON, #50H
            MOV   AUXR, #40H
            MOV   TMOD, #00H
            MOV   TH1, #HIGH UARTBAUD
            MOV   TL1, #LOW UARTBAUD
            SETB    TR1
            SETB    ES
            SETB    EA
MAIN_LOOP:
            ; 用户应用程序
            SJMP    MAIN_LOOP
            END
</code></pre>
<blockquote>
<p><strong>说明</strong>:此方式不依赖USB,所有带串口的STC单片机均可使用。关键语句是 <code>IAP_CONTR = 0x60;</code>(C语言)或 <code>MOV IAP_CONTR, #60H</code>(汇编),执行后MCU自动复位到ISP系统区开始下载。</p>
</blockquote>
<hr />
<h3>方式三:IAP系列单片机自编程方式(最灵活,可定制)</h3>
<p><strong>适用芯片</strong>:STC IAP系列单片机(如STC8H8K64U、STC8H1K28等)</p>
<p><strong>原理</strong>:使用STC的IAP系列单片机,用户可自行编写完整的ISP下载程序,将整个Flash空间(含用户程序区)设置为EEPROM,实现用户程序对自身程序存储区的在线更新。</p>
<p><strong>参考代码(C语言,用户代码部分)</strong>:</p>
<pre><code class="language-c">// IAP系列单片机用户代码 - 检测到下载条件后跳转到ISP模块
// 测试工作频率:11.0592MHz

#include &quot;stc8h.h&quot;

#define FOSC       11059200L
#define BAUD       (65536 - (FOSC/115200+2)/4)
#define ISPPROGRAM 0xFA00      // ISP下载程序入口地址

void (*IspProgram)() = ISPPROGRAM;// 定义函数指针

char cnt7f;// 接收7F计数器

void uart() interrupt 4      // 串口中断服务程序
{
    if(TI) TI = 0;             // 发送完成中断
    if(RI)                     // 接收完成中断
    {
      if(SBUF == 0x7F)
      {
            cnt7f++;
            if(cnt7f &gt;= 16)
            {
                IspProgram();// ★ 跳转到ISP下载模块,实现不停电下载
            }
      }
      else
      {
            cnt7f = 0;
      }
      RI = 0;
    }
}

void main()
{
    P_SW2 |= 0x80;

    SCON = 0x50;
    AUXR = 0x40;
    TH1 = BAUD &gt;&gt; 8;
    TL1 = BAUD;
    TR1 = 1;

    ES = 1;
    EA = 1;

    while(1)
    {
      // 用户应用程序
    }
}
</code></pre>
<blockquote>
<p><strong>说明</strong>:此方式需要将芯片的全部Flash空间设置为EEPROM,用户自行编写ISP引导程序和用户程序。当用户程序检测到连续16个 <code>0x7F</code> 同步头后,通过函数指针跳转到ISP模块(地址 <code>0xFA00</code>),由ISP模块完成固件更新。</p>
</blockquote>
<hr />
<h3>三种方式对比总结</h3>
<table>
<thead>
<tr>
<th>方式</th>
<th>适用芯片</th>
<th>复杂度</th>
<th>推荐场景</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>① USB库函数方式</strong></td>
<td>带USB的STC系列</td>
<td>⭐ 最简单</td>
<td><strong>最推荐</strong>,开发阶段首选</td>
</tr>
<tr>
<td><strong>② 串口自定义命令方式</strong></td>
<td>所有带串口的STC</td>
<td>⭐⭐ 简单</td>
<td>通用性强,无USB芯片也可用</td>
</tr>
<tr>
<td><strong>③ IAP自编程方式</strong></td>
<td>IAP系列单片机</td>
<td>⭐⭐⭐ 较复杂</td>
<td>需要深度定制ISP流程时</td>
</tr>
</tbody>
</table>
<p><strong>信息来源</strong>:</p>
<ul>
<li>STC8H手册第3章「USB库函数使用说明,简单的USB-CDC虚拟串口通信,USB不停电下载」</li>
<li>STC8H手册附录V「运行用户程序时收到用户命令后自动启动ISP下载(不停电)」</li>
<li>STC8H手册附录W「使用STC的IAP系列单片机开发自己的ISP程序」</li>
<li>Ai8手册第3章「USB库函数使用说明,简单的USB-CDC虚拟串口通信,USB不停电下载」</li>
<li>STC32G手册第3章「USB库函数使用说明,简单的USB-CDC虚拟串口通信,USB不停电下载」</li>
</ul>
<p>如果您需要针对某个具体型号的完整工程代码,请告知具体芯片型号,我可以提供更精确的配置。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=USB%E4%B8%8D%E5%81%9C%E7%94%B5%E4%B8%8B%E8%BD%BD%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%E6%8E%A8%E8%8D%90%EF%BC%9F%0A%E7%94%9F%E6%88%90%E5%AF%B9%E5%BA%94%E7%9A%84%E5%8F%82%E8%80%83%E7%A8%8B%E5%BA%8F%EF%BC%9F&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;USB不停电下载的几种方式推荐?<br />
生成对应的参考程序?&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24057&amp;highlight=USB%E4%B8%8D%E5%81%9C%E7%94%B5%E4%B8%8B%E8%BD%BD%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%E6%8E%A8%E8%8D%90%EF%BC%9F%0A%E7%94%9F%E6%88%90%E5%AF%B9%E5%BA%94%E7%9A%84%E5%8F%82%E8%80%83%E7%A8%8B%E5%BA%8F%EF%BC%9F&amp;page=63&amp;extra=#pid222097">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=21356&amp;highlight=USB%E4%B8%8D%E5%81%9C%E7%94%B5%E4%B8%8B%E8%BD%BD%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%E6%8E%A8%E8%8D%90%EF%BC%9F%0A%E7%94%9F%E6%88%90%E5%AF%B9%E5%BA%94%E7%9A%84%E5%8F%82%E8%80%83%E7%A8%8B%E5%BA%8F%EF%BC%9F&amp;page=1&amp;extra=#pid196995">Ai8051U学习打卡之旅</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=21766&amp;highlight=USB%E4%B8%8D%E5%81%9C%E7%94%B5%E4%B8%8B%E8%BD%BD%E7%9A%84%E5%87%A0%E7%A7%8D%E6%96%B9%E5%BC%8F%E6%8E%A8%E8%8D%90%EF%BC%9F%0A%E7%94%9F%E6%88%90%E5%AF%B9%E5%BA%94%E7%9A%84%E5%8F%82%E8%80%83%E7%A8%8B%E5%BA%8F%EF%BC%9F&amp;page=1&amp;extra=#pid200714">AI8051U实验箱学习记录</a></li>
</ol>
<p>⏱️ 查找用时 10.9s | 总回答用时 32.9s</p>

芯启航 发表于 2026-7-2 09:21:50

请基于 STC8H8K64U 编写完整演示程序,需包含以下功能:

[*]串口中断接收上位机指令,触发多通道 ADC 循环采集,采集电压值通过串口定时上传;
[*]两组互补硬件 PWM 输出,串口下发数值动态调整占空比,控制直流电机正反转调速;
[*]上电读取片内 EEPROM 保存的电机转速参数,运行中修改转速后自动存入 EEPROM 实现掉电记忆;
[*]附加硬件比较器做过流检测,异常时自动锁死 PWM 并串口上报故障。




芯启航 发表于 2026-7-2 09:22:10

<p>手册信息已获取完毕。现在我来编写完整的演示程序。</p>
<p>以下是为 <strong>STC8H8K64U</strong> 编写的完整演示程序,包含您要求的所有功能。程序基于手册中的范例代码整合而成,主频设为 <strong>11.0592MHz</strong>(串口波特率精准),使用 <strong>Keil C251</strong> 编译。</p>
<hr />
<h2>完整演示程序</h2>
<pre><code class="language-c">/*------------------------------------------------------------------*/
/* --- STC8H8K64U 综合演示程序                                    */
/* 功能:                                                            */
/*   1. 串口1中断接收上位机指令,触发多通道ADC循环采集,电压值定时上传 */
/*   2. PWMA两组互补PWM输出,串口下发数值动态调整占空比               */
/*   3. 上电读取EEPROM保存的电机转速参数,修改后自动存入EEPROM      */
/*   4. 硬件比较器做过流检测,异常时锁死PWM并串口上报故障             */
/*                                                                  */
/* 主频:11.0592MHz                                                */
/* 串口:115200,8,n,1 (P3.0/P3.1)                                 */
/* ADC:P1.0~P1.3 (4通道)                                           */
/* PWM:PWMA通道1互补输出 P1.0/P1.1,通道2互补输出 P1.2/P1.3         */
/* 比较器:P3.6(CMP+), 内部BGV(CMP-) 过流检测                        */
/* EEPROM:扇区0保存转速参数                                          */
/*------------------------------------------------------------------*/

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

#define MAIN_Fosc      11059200L
#define Baudrate1      115200UL
#define BRT1             (65536UL - (MAIN_Fosc / 4) / Baudrate1)

/* ---------- 用户指令定义 ---------- */
#define CMD_ADC_START   0xA1      // 启动ADC采集
#define CMD_PWM_SET   0xB1      // 设置PWM占空比
#define CMD_MOTOR_SPEED 0xC1      // 设置电机转速
#define CMD_READ_EEPROM 0xD1      // 读取EEPROM参数

/* ---------- EEPROM地址定义 ---------- */
#define EEPROM_ADDR_SPEED0x0000// 电机转速参数存放地址(2字节)

/* ---------- 全局变量 ---------- */
bitB_TX1_Busy;                  // 串口发送忙标志
u8   RX1_Buffer;            // 串口接收缓冲
u8   RX1_Cnt;                     // 接收计数
u8   RX1_TimeOut;               // 接收超时
bitB_RX1_OK;                  // 一帧数据接收完成标志

u16ADC_Result;               // 4通道ADC结果
bitB_ADC_Complete;            // ADC采集完成标志
u8   ADC_Channel;               // 当前ADC通道(0~3)

u16PWM1_Duty;                   // PWM1占空比
u16PWM2_Duty;                   // PWM2占空比
u16Motor_Speed;               // 电机转速参数(0~1000)

bitB_Fault;                     // 故障标志
bitB_Fault_Reported;            // 故障已上报标志

/* ---------- 函数声明 ---------- */
void UART1_Init(void);
void UART1_SendByte(u8 dat);
void UART1_SendString(u8 *p);
void UART1_PrintHex(u16 val);
void ADC_Init(void);
void ADC_Start(u8 channel);
void PWMA_Init(void);
void PWMA_SetDuty(u16 duty1, u16 duty2);
void PWMA_OutputEnable(void);
void PWMA_OutputDisable(void);
void CMP_Init(void);
void EEPROM_Read(u16 addr, u8 *p, u8 len);
void EEPROM_Write(u16 addr, u8 *p, u8 len);
void EEPROM_EraseSector(u16 addr);
void Timer0_Init(void);
void delay_ms(u16 ms);

/* ================================================================ */
/*                        主函数                                     */
/* ================================================================ */
void main(void)
{
    u8 i;
    u16 temp_speed;

    P_SW2 |= 0x80;                // 使能访问XFR
    P0M0 = 0x00; P0M1 = 0x00;
    P1M0 = 0x00; P1M1 = 0x00;
    P2M0 = 0x00; P2M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
    P4M0 = 0x00; P4M1 = 0x00;
    P5M0 = 0x00; P5M1 = 0x00;
    P6M0 = 0x00; P6M1 = 0x00;
    P7M0 = 0x00; P7M1 = 0x00;

    UART1_Init();               // 串口初始化
    ADC_Init();                   // ADC初始化
    PWMA_Init();                  // PWM初始化
    CMP_Init();                   // 比较器初始化
    Timer0_Init();                // 定时器0(1ms时基)

    /* ---- 上电读取EEPROM中的电机转速参数 ---- */
    EEPROM_Read(EEPROM_ADDR_SPEED, (u8 *)&amp;temp_speed, 2);
    if((temp_speed == 0) || (temp_speed &gt; 1000))
      Motor_Speed = 500;      // 默认值500
    else
      Motor_Speed = temp_speed;

    /* ---- 根据EEPROM转速参数设置初始PWM占空比 ---- */
    PWM1_Duty = Motor_Speed;      // 占空比 = 转速值(0~1000)
    PWM2_Duty = Motor_Speed;
    PWMA_SetDuty(PWM1_Duty, PWM2_Duty);
    PWMA_OutputEnable();

    EA = 1;                     // 开总中断

    UART1_SendString(&quot;STC8H8K64U Demo Start!\r\n&quot;);
    UART1_SendString(&quot;Motor Speed from EEPROM: &quot;);
    UART1_PrintHex(Motor_Speed);
    UART1_SendString(&quot;\r\n&quot;);

    /* ---- 主循环 ---- */
    while(1)
    {
      /* ---- 串口指令解析 ---- */
      if(B_RX1_OK)
      {
            B_RX1_OK = 0;
            if(RX1_Cnt &gt;= 2)      // 至少2字节: 命令 + 数据
            {
                switch(RX1_Buffer)
                {
                  case CMD_ADC_START:       // 启动ADC循环采集
                        ADC_Channel = 0;
                        B_ADC_Complete = 0;
                        ADC_Start(ADC_Channel);
                        UART1_SendString(&quot;ADC Start\r\n&quot;);
                        break;

                  case CMD_PWM_SET:         // 设置PWM占空比
                        PWM1_Duty = ((u16)RX1_Buffer &lt;&lt; 8) | RX1_Buffer;
                        PWM2_Duty = ((u16)RX1_Buffer &lt;&lt; 8) | RX1_Buffer;
                        if(PWM1_Duty &gt; 1000) PWM1_Duty = 1000;
                        if(PWM2_Duty &gt; 1000) PWM2_Duty = 1000;
                        PWMA_SetDuty(PWM1_Duty, PWM2_Duty);
                        UART1_SendString(&quot;PWM Set OK\r\n&quot;);
                        break;

                  case CMD_MOTOR_SPEED:   // 设置电机转速并存入EEPROM
                        Motor_Speed = ((u16)RX1_Buffer &lt;&lt; 8) | RX1_Buffer;
                        if(Motor_Speed &gt; 1000) Motor_Speed = 1000;
                        PWM1_Duty = Motor_Speed;
                        PWM2_Duty = Motor_Speed;
                        PWMA_SetDuty(PWM1_Duty, PWM2_Duty);
                        /* ---- 自动存入EEPROM实现掉电记忆 ---- */
                        EEPROM_EraseSector(EEPROM_ADDR_SPEED);
                        EEPROM_Write(EEPROM_ADDR_SPEED, (u8 *)&amp;Motor_Speed, 2);
                        UART1_SendString(&quot;Speed Set &amp; Saved: &quot;);
                        UART1_PrintHex(Motor_Speed);
                        UART1_SendString(&quot;\r\n&quot;);
                        break;

                  case CMD_READ_EEPROM:   // 读取EEPROM参数
                        EEPROM_Read(EEPROM_ADDR_SPEED, (u8 *)&amp;temp_speed, 2);
                        UART1_SendString(&quot;EEPROM Speed: &quot;);
                        UART1_PrintHex(temp_speed);
                        UART1_SendString(&quot;\r\n&quot;);
                        break;

                  default:
                        UART1_SendString(&quot;Unknown CMD\r\n&quot;);
                        break;
                }
            }
            RX1_Cnt = 0;
      }

      /* ---- ADC采集完成,启动下一通道或上传数据 ---- */
      if(B_ADC_Complete)
      {
            B_ADC_Complete = 0;
            ADC_Result = ((u16)ADC_RES &lt;&lt; 8) | ADC_RESL;

            ADC_Channel++;
            if(ADC_Channel &lt; 4)
            {
                ADC_Start(ADC_Channel);       // 继续下一通道
            }
            else
            {
                /* ---- 4通道采集完成,通过串口上传 ---- */
                UART1_SendString(&quot;ADC: &quot;);
                for(i=0; i&lt;4; i++)
                {
                  UART1_PrintHex(ADC_Result);
                  UART1_SendString(&quot; &quot;);
                }
                UART1_SendString(&quot;\r\n&quot;);
                ADC_Channel = 0;            // 复位,等待下次指令
            }
      }

      /* ---- 故障处理:锁死PWM并上报 ---- */
      if(B_Fault)
      {
            if(!B_Fault_Reported)
            {
                B_Fault_Reported = 1;
                PWMA_OutputDisable();         // 锁死PWM输出
                UART1_SendString(&quot;FAULT: Overcurrent! PWM Locked!\r\n&quot;);
            }
      }
    }
}

/* ================================================================ */
/*                      串口1初始化 (115200)                        */
/* ================================================================ */
void UART1_Init(void)
{
    SCON = 0x50;                  // 模式1, 8位数据, 允许接收
    T2L = (u8)BRT1;
    T2H = (u8)(BRT1 &gt;&gt; 8);
    AUXR = 0x15;                  // T2运行, 1T模式, 串口1选择T2做波特率
    ES = 1;                     // 使能串口中断
    B_TX1_Busy = 0;
    RX1_Cnt = 0;
    RX1_TimeOut = 0;
    B_RX1_OK = 0;
}

/* ---- 串口发送一个字节(查询方式) ---- */
void UART1_SendByte(u8 dat)
{
    B_TX1_Busy = 1;
    SBUF = dat;
    while(B_TX1_Busy);
}

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

/* ---- 串口发送16位十六进制数 ---- */
void UART1_PrintHex(u16 val)
{
    u8 i, nibble;
    UART1_SendByte('0');
    UART1_SendByte('x');
    for(i=0; i&lt;4; i++)
    {
      nibble = (u8)((val &gt;&gt; (12 - i*4)) &amp; 0x0F);
      if(nibble &lt; 10)
            UART1_SendByte('0' + nibble);
      else
            UART1_SendByte('A' + nibble - 10);
    }
}

/* ================================================================ */
/*                      串口1中断服务函数                           */
/* ================================================================ */
void UART1_ISR(void) interrupt 4
{
    if(TI)
    {
      TI = 0;
      B_TX1_Busy = 0;
    }
    if(RI)
    {
      RI = 0;
      if(RX1_Cnt &lt; sizeof(RX1_Buffer))
      {
            RX1_Buffer = SBUF;
            RX1_TimeOut = 10;   // 约10ms超时
      }
    }
}

/* ================================================================ */
/*                      ADC初始化 (12位)                           */
/* ================================================================ */
void ADC_Init(void)
{
    ADCCFG = 0x20;                // 右对齐, ADC时钟 = SYSclk/2/2
    ADC_CONTR = 0x80;             // 开启ADC电源
    delay_ms(1);                  // 等待ADC电源稳定
    EADC = 1;                     // 使能ADC中断
}

/* ---- 启动ADC转换 ---- */
void ADC_Start(u8 channel)
{
    ADC_CONTR = 0x80 | 0x40 | (channel &amp; 0x0F);// 启动转换
}

/* ---- ADC中断服务函数 ---- */
void ADC_ISR(void) interrupt 5
{
    ADC_CONTR &amp;= ~0x20;         // 清ADC_FLAG
    B_ADC_Complete = 1;
}

/* ================================================================ */
/*               PWMA高级PWM初始化 (互补输出, 带死区)               */
/* ================================================================ */
void PWMA_Init(void)
{
    /* ---- 关闭通道,先配置 ---- */
    PWMA_CCER1 = 0x00;
    PWMA_CCER2 = 0x00;
    PWMA_ENO = 0x00;

    /* ---- 预分频: PWM时钟 = 11.0592MHz / (10+1) ≈ 1MHz ---- */
    PWMA_PSCRH = 0;
    PWMA_PSCRL = 10;

    /* ---- PWM周期 = 1000 (1ms), 边沿对齐 ---- */
    PWMA_ARRH = (u8)((1000-1) &gt;&gt; 8);
    PWMA_ARRL = (u8)(1000-1);

    /* ---- 通道1: PWM模式1, 预装载使能 ---- */
    PWMA_CCMR1 = 0x68;            // OC1M=110(PWM模式1), OC1PE=1
    PWMA_CCR1 = 500;            // 初始占空比50%
    PWMA_CCER1 |= 0x05;         // CC1E=1, CC1P=0 (高有效)

    /* ---- 通道2: PWM模式1, 预装载使能 ---- */
    PWMA_CCMR2 = 0x68;
    PWMA_CCR2 = 500;
    PWMA_CCER1 |= 0x50;         // CC2E=1, CC2P=0

    /* ---- 死区时间: 约1us (DTG=1, tCK_PSC≈1us) ---- */
    PWMA_DTR = 1;

    /* ---- 输出脚选择: 通道1-&gt;P1.0/P1.1, 通道2-&gt;P1.2/P1.3 ---- */
    PWMA_PS = 0x00;               // 默认P1口

    /* ---- 使能主输出 ---- */
    PWMA_BKR = 0x80;            // MOE=1, 主输出使能

    /* ---- 启动计数器 ---- */
    PWMA_CR1 = 0x81;            // ARPE=1, CEN=1
}

/* ---- 设置PWMA占空比 ---- */
void PWMA_SetDuty(u16 duty1, u16 duty2)
{
    if(duty1 &gt; 1000) duty1 = 1000;
    if(duty2 &gt; 1000) duty2 = 1000;
    PWMA_CCR1 = duty1;
    PWMA_CCR2 = duty2;
}

/* ---- 使能PWM输出 ---- */
void PWMA_OutputEnable(void)
{
    PWMA_ENO |= 0x05;             // ENO1P=1, ENO2P=1
    PWMA_BKR |= 0x80;             // MOE=1
}

/* ---- 禁止PWM输出(锁死) ---- */
void PWMA_OutputDisable(void)
{
    PWMA_BKR &amp;= ~0x80;            // MOE=0, 输出强制为无效电平
    PWMA_ENO = 0x00;            // 禁止所有PWM输出
}

/* ================================================================ */
/*                   硬件比较器初始化 (过流检测)                     */
/* ================================================================ */
void CMP_Init(void)
{
    /* 比较器正端(PIS): P3.6 (过流检测信号输入)                        */
    /* 比较器负端(NIS): 内部BandGap电压 1.19V (作为阈值)               */
    /* 当P3.6电压 &gt; 1.19V 时, CMPRES=1, 触发下降沿中断(过流)          */

    CMPCR2 = 0x00;                // 滤波关闭
    CMPCR1 = 0x80;                // CMPEN=1, 使能比较器
    CMPCR1 |= 0x00;               // PIS=0: P3.6为正端
    CMPCR1 |= 0x04;               // NIS=1: P3.7为负端 (实际使用内部BGV需配置CMPEXCFG)
    /* 注: 使用内部BGV做负端需设置CMPEXCFG寄存器, 此处简化使用P3.7外部参考 */

    /* 使能下降沿中断(过流时CMPRES从1变0或从0变1, 根据实际电路调整) */
    CMPCR1 |= 0x10;               // NIE=1, 下降沿中断
    // CMPCR1 |= 0x20;            // PIE=1, 上升沿中断(可选)

    /* 设置比较器中断优先级 */
    IP2 |= 0x20;                  // PCMP=1, 高优先级
}

/* ---- 比较器中断服务函数 ---- */
void CMP_ISR(void) interrupt 21
{
    CMPCR1 &amp;= ~0x40;            // 清CMPIF中断标志
    B_Fault = 1;                  // 置故障标志
}

/* ================================================================ */
/*                   EEPROM操作函数 (IAP方式)                         */
/* ================================================================ */

/* ---- EEPROM读操作 ---- */
void EEPROM_Read(u16 addr, u8 *p, u8 len)
{
    u8 i;
    for(i=0; i&lt;len; i++)
    {
      IAP_ADDRH = (u8)(addr &gt;&gt; 8);
      IAP_ADDRL = (u8)(addr);
      IAP_CMD = 0x01;         // 读命令
      IAP_CONTR = 0x80;         // IAPEN=1
      IAP_TPS = 11;             // 11.0592MHz -&gt; 11

      EA = 0;
      IAP_TRIG = 0x5A;
      IAP_TRIG = 0xA5;
      _nop_();
      EA = 1;

      *p++ = IAP_DATA;
      addr++;
    }
    IAP_CONTR = 0x00;             // 关闭IAP
}

/* ---- EEPROM写操作(需先擦除) ---- */
void EEPROM_Write(u16 addr, u8 *p, u8 len)
{
    u8 i;
    for(i=0; i&lt;len; i++)
    {
      IAP_ADDRH = (u8)(addr &gt;&gt; 8);
      IAP_ADDRL = (u8)(addr);
      IAP_DATA = *p++;
      IAP_CMD = 0x02;         // 写命令
      IAP_CONTR = 0x80;         // IAPEN=1
      IAP_TPS = 11;

      EA = 0;
      IAP_TRIG = 0x5A;
      IAP_TRIG = 0xA5;
      _nop_();
      EA = 1;

      addr++;
    }
    IAP_CONTR = 0x00;
}

/* ---- EEPROM扇区擦除(512字节) ---- */
void EEPROM_EraseSector(u16 addr)
{
    IAP_ADDRH = (u8)(addr &gt;&gt; 8);
    IAP_ADDRL = (u8)(addr);
    IAP_CMD = 0x03;               // 擦除命令
    IAP_CONTR = 0x80;
    IAP_TPS = 11;

    EA = 0;
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    _nop_();
    EA = 1;

    IAP_CONTR = 0x00;
}

/* ================================================================ */
/*                   定时器0初始化 (1ms时基)                        */
/* ================================================================ */
void Timer0_Init(void)
{
    AUXR |= 0x80;               // T0 1T模式
    TMOD &amp;= 0xF0;
    TH0 = (u8)((65536UL - MAIN_Fosc / 1000) &gt;&gt; 8);
    TL0 = (u8)(65536UL - MAIN_Fosc / 1000);
    ET0 = 1;
    TR0 = 1;
}

/* ---- 定时器0中断(1ms) ---- */
void Timer0_ISR(void) interrupt 1
{
    /* 串口接收超时处理 */
    if(RX1_TimeOut &gt; 0)
    {
      if(--RX1_TimeOut == 0)
      {
            if(RX1_Cnt &gt; 0)
                B_RX1_OK = 1;   // 接收完成
      }
    }
}

/* ================================================================ */
/*                     延时函数                                    */
/* ================================================================ */
void delay_ms(u16 ms)
{
    u16 i;
    while(ms--)
    {
      i = MAIN_Fosc / 10000;
      while(--i);
    }
}
</code></pre>
<hr />
<h2>程序说明</h2>
<h3>1. 串口通信协议</h3>
<table>
<thead>
<tr>
<th align="center">命令字节</th>
<th align="left">数据格式</th>
<th align="left">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><code>0xA1</code></td>
<td align="left">无数据</td>
<td align="left">启动4通道ADC循环采集,结果通过串口上传</td>
</tr>
<tr>
<td align="center"><code>0xB1</code></td>
<td align="left">2字节高8位PWM1占空比 + 2字节高8位PWM2占空比</td>
<td align="left">设置PWMA通道1/2占空比(0~1000)</td>
</tr>
<tr>
<td align="center"><code>0xC1</code></td>
<td align="left">2字节高8位转速值(0~1000)</td>
<td align="left">设置电机转速,自动存入EEPROM</td>
</tr>
<tr>
<td align="center"><code>0xD1</code></td>
<td align="left">无数据</td>
<td align="left">读取EEPROM中保存的转速参数</td>
</tr>
</tbody>
</table>
<p><strong>串口参数</strong>:115200, 8, n, 1 (P3.0 TxD, P3.1 RxD)</p>
<h3>2. ADC多通道采集</h3>
<ul>
<li>使用 <strong>P1.0~P1.3</strong> 共4个模拟输入通道</li>
<li>12位分辨率,右对齐</li>
<li>收到 <code>0xA1</code> 指令后启动循环采集,4通道全部完成后通过串口上传结果</li>
</ul>
<h3>3. 互补PWM输出</h3>
<ul>
<li><strong>PWMA通道1</strong>:P1.0(PWM1P) / P1.1(PWM1N) 互补输出</li>
<li><strong>PWMA通道2</strong>:P1.2(PWM2P) / P1.3(PWM2N) 互补输出</li>
<li>PWM频率 ≈ <strong>1kHz</strong>(周期1000,时钟1MHz)</li>
<li>死区时间 ≈ <strong>1μs</strong></li>
<li>占空比范围 0~1000(对应0%~100%)</li>
<li>通过串口下发 <code>0xB1</code> + 4字节数据动态调整</li>
</ul>
<h3>4. EEPROM掉电记忆</h3>
<ul>
<li>上电自动从EEPROM扇区0地址 <code>0x0000</code> 读取2字节转速参数</li>
<li>收到 <code>0xC1</code> 指令修改转速后,自动擦除扇区并写入新值</li>
<li>默认值500(占空比50%)</li>
</ul>
<h3>5. 硬件比较器过流保护</h3>
<ul>
<li><strong>正端(PIS)</strong>:P3.6(外部过流检测信号)</li>
<li><strong>负端(NIS)</strong>:P3.7(参考电压,或通过CMPEXCFG配置为内部BGV 1.19V)</li>
<li>当检测到过流信号时,比较器触发下降沿中断</li>
<li>中断服务中置故障标志,主循环检测到故障后:
<ul>
<li>清除MOE位,锁死PWM输出(输出强制为无效电平)</li>
<li>通过串口上报 <code>&quot;FAULT: Overcurrent! PWM Locked!&quot;</code></li>
</ul>
</li>
</ul>
<h3>6. 引脚分配汇总</h3>
<table>
<thead>
<tr>
<th align="center">引脚</th>
<th align="left">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">P1.0</td>
<td align="left">PWM1P (互补输出正端)</td>
</tr>
<tr>
<td align="center">P1.1</td>
<td align="left">PWM1N (互补输出负端)</td>
</tr>
<tr>
<td align="center">P1.2</td>
<td align="left">PWM2P (互补输出正端)</td>
</tr>
<tr>
<td align="center">P1.3</td>
<td align="left">PWM2N (互补输出负端)</td>
</tr>
<tr>
<td align="center">P1.0~P1.3</td>
<td align="left">ADC输入通道0~3</td>
</tr>
<tr>
<td align="center">P3.0</td>
<td align="left">UART1 TxD</td>
</tr>
<tr>
<td align="center">P3.1</td>
<td align="left">UART1 RxD</td>
</tr>
<tr>
<td align="center">P3.6</td>
<td align="left">比较器正端(过流检测输入)</td>
</tr>
<tr>
<td align="center">P3.7</td>
<td align="left">比较器负端(参考电压输入)</td>
</tr>
</tbody>
</table>
<hr />
<p><strong>使用说明</strong>:</p>
<ol>
<li>在 <strong>AiCube-ISP</strong> 下载软件中选择芯片 <strong>STC8H8K64U</strong>,主频选择 <strong>11.0592MHz</strong></li>
<li>使用串口助手(115200)发送命令:
<ul>
<li>发送 <code>A1</code> → 启动ADC采集</li>
<li>发送 <code>B1 03 E8 01 F4</code> → PWM1占空比1000(100%),PWM2占空比500(50%)</li>
<li>发送 <code>C1 01 90</code> → 设置转速400并存入EEPROM</li>
<li>发送 <code>D1</code> → 读取EEPROM中的转速值</li>
</ul>
</li>
<li>过流时PWM自动锁死,串口收到故障报告</li>
</ol>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC8H8K64U%20%E4%B8%B2%E5%8F%A3%E4%B8%AD%E6%96%AD%20%E5%A4%9A%E9%80%9A%E9%81%93ADC%20%E5%BE%AA%E7%8E%AF%E9%87%87%E9%9B%86&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC8H8K64U 串口中断 多通道ADC 循环采集&quot;</a></p>
<p>⏱️ 查找用时 8.3s | 总回答用时 71.0s</p>

芯风起 发表于 2026-7-2 09:24:03

帮AI8H2K32U-45I-LQFP32自动写一个完整的演示程序,用途:
车灯的切换和流水

芯风起 发表于 2026-7-2 09:24:11

<p>好的,我已经获取了足够的信息。现在让我编写一个完整的车灯切换和流水灯演示程序。</p>
<p>根据手册,<strong>Ai8H2K32U-45I-LQFP32</strong> 的引脚定义如下(LQFP32封装,共32个引脚):</p>
<ul>
<li>注意:P3.0/P3.1 上电后为准双向口(串口下载用),其余I/O上电后为高阻输入,需先配置模式</li>
</ul>
<p>下面是为您编写的完整演示程序:</p>
<hr />
<h2>Ai8H2K32U-45I-LQFP32 车灯切换与流水灯演示程序</h2>
<h3>硬件接线说明</h3>
<p>根据 LQFP32 封装引脚,建议如下接线(您可以根据实际硬件调整):</p>
<table>
<thead>
<tr>
<th align="center">功能</th>
<th align="center">引脚</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><strong>左转向灯</strong></td>
<td align="center">P1.0</td>
<td align="left">LED正极→限流电阻→P1.0,负极接GND</td>
</tr>
<tr>
<td align="center"><strong>右转向灯</strong></td>
<td align="center">P1.1</td>
<td align="left">同上</td>
</tr>
<tr>
<td align="center"><strong>近光灯</strong></td>
<td align="center">P1.4</td>
<td align="left">同上</td>
</tr>
<tr>
<td align="center"><strong>远光灯</strong></td>
<td align="center">P1.5</td>
<td align="left">同上</td>
</tr>
<tr>
<td align="center"><strong>刹车灯</strong></td>
<td align="center">P1.6</td>
<td align="left">同上</td>
</tr>
<tr>
<td align="center"><strong>倒车灯</strong></td>
<td align="center">P1.7</td>
<td align="left">同上</td>
</tr>
<tr>
<td align="center"><strong>流水灯1</strong></td>
<td align="center">P2.0</td>
<td align="left">流水灯段1</td>
</tr>
<tr>
<td align="center"><strong>流水灯2</strong></td>
<td align="center">P2.1</td>
<td align="left">流水灯段2</td>
</tr>
<tr>
<td align="center"><strong>流水灯3</strong></td>
<td align="center">P2.2</td>
<td align="left">流水灯段3</td>
</tr>
<tr>
<td align="center"><strong>流水灯4</strong></td>
<td align="center">P2.3</td>
<td align="left">流水灯段4</td>
</tr>
<tr>
<td align="center"><strong>模式按键</strong></td>
<td align="center">P3.2</td>
<td align="left">按键→GND(外部中断0,下降沿触发)</td>
</tr>
<tr>
<td align="center"><strong>切换按键</strong></td>
<td align="center">P3.3</td>
<td align="left">按键→GND(外部中断1,下降沿触发)</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注意</strong>:Ai8H2K32U 的 I/O 口推挽输出时驱动能力可达20mA,LED需串联合适的限流电阻(如330Ω~1kΩ,取决于LED和电压)。</p>
</blockquote>
<hr />
<h3>完整C语言源代码</h3>
<pre><code class="language-c">/*------------------------------------------------------------------*/
/* --- STC MCU International Limited -------------------------------*/
/* --- Ai8H2K32U - 车灯切换与流水灯演示程序 -----------------------*/
/* --- 工作频率: 内部IRC 11.0592MHz (使用STC-ISP软件设置) ---------*/
/* --- 硬件平台: Ai8H2K32U-45I-LQFP32 -----------------------------*/
/*------------------------------------------------------------------*/

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

// 定义按键引脚
#define KEY_MODE   P32   // 模式切换按键 (P3.2)
#define KEY_SWITCH P33   // 功能切换按键 (P3.3)

// 定义车灯控制引脚 (P1口 - 推挽输出)
#define LED_LEFT   P10   // 左转向灯
#define LED_RIGHTP11   // 右转向灯
#define LED_LOW    P14   // 近光灯
#define LED_HIGH   P15   // 远光灯
#define LED_BRAKEP16   // 刹车灯
#define LED_BACK   P17   // 倒车灯

// 定义流水灯引脚 (P2口 - 推挽输出)
#define LED_FLOW1P20
#define LED_FLOW2P21
#define LED_FLOW3P22
#define LED_FLOW4P23

// 灯效模式枚举
typedef enum {
    MODE_ALL_OFF = 0,       // 全部关闭
    MODE_LOW_BEAM,          // 近光灯
    MODE_HIGH_BEAM,         // 远光灯
    MODE_LEFT_TURN,         // 左转向灯(闪烁)
    MODE_RIGHT_TURN,      // 右转向灯(闪烁)
    MODE_HAZARD,            // 双闪(危险报警)
    MODE_BRAKE,             // 刹车灯
    MODE_REVERSE,         // 倒车灯
    MODE_FLOW_LEFT,         // 流水灯向左
    MODE_FLOW_RIGHT,      // 流水灯向右
    MODE_FLOW_BREATH,       // 流水灯呼吸效果
    MODE_MAX
} LIGHT_MODE;

// 全局变量
volatile LIGHT_MODE g_current_mode = MODE_ALL_OFF;
volatile uint8_t g_tick_10ms = 0;       // 10ms时基计数器
volatile uint8_t g_blink_counter = 0;   // 闪烁计数器
volatile uint8_t g_flow_index = 0;      // 流水灯索引
volatile bit g_blink_state = 0;         // 闪烁状态
volatile bit g_flow_direction = 0;      // 流水方向: 0=左移, 1=右移
volatile uint8_t g_breath_value = 0;    // 呼吸值(0~100)
volatile bit g_breath_dir = 0;          // 呼吸方向: 0=增, 1=减

// 函数声明
void System_Init(void);
void GPIO_Init(void);
void Timer0_Init(void);
void EXTI_Init(void);
void Delay10ms(uint16_t ms);
void SetAllLightsOff(void);
void Light_Show(void);
void Flow_Left(void);
void Flow_Right(void);
void Flow_Breath(void);

/*------------------------------------------------------------------*/
/* 主函数                                                         */
/*------------------------------------------------------------------*/
void main(void)
{
    System_Init();

    // 初始状态:全部关闭
    SetAllLightsOff();
    g_current_mode = MODE_ALL_OFF;

    while(1)
    {
      // 每10ms执行一次灯效刷新
      if(g_tick_10ms &gt;= 1)
      {
            g_tick_10ms = 0;
            Light_Show();
      }
    }
}

/*------------------------------------------------------------------*/
/* 系统初始化                                                       */
/*------------------------------------------------------------------*/
void System_Init(void)
{
    // 使能访问XFR扩展寄存器
    P_SW2 |= 0x80;

    GPIO_Init();
    Timer0_Init();
    EXTI_Init();

    // 开启总中断
    EA = 1;
}

/*------------------------------------------------------------------*/
/* GPIO初始化                                                       */
/*------------------------------------------------------------------*/
void GPIO_Init(void)
{
    // P1口全部设为推挽输出 (车灯控制)
    P1M0 = 0xFF;
    P1M1 = 0x00;

    // P2.0~P2.3设为推挽输出 (流水灯)
    P2M0 = 0x0F;    // 仅低4位推挽输出
    P2M1 = 0x00;

    // P3.2, P3.3设为高阻输入 (按键)
    P3M0 = 0x00;
    P3M1 = 0x0C;    // P3.2和P3.3高阻输入

    // 开启P3.2和P3.3内部4K上拉电阻 (使按键输入更稳定)
    P3PU |= 0x0C;

    // 初始全部关闭
    P1 = 0x00;
    P2 &amp;= 0xF0;   // 仅清低4位
}

/*------------------------------------------------------------------*/
/* 定时器0初始化 - 10ms中断                                          */
/* 使用24位自动重装载模式                                           */
/* 时钟源: SYSclk/12 = 11.0592MHz/12 = 921.6kHz                  */
/* 定时10ms: 921600Hz * 0.01s = 9216                              */
/* 重装载值: 65536 - 9216 = 56320 = 0xDC00                        */
/*------------------------------------------------------------------*/
void Timer0_Init(void)
{
    // 定时器0 16位自动重装载模式 (模式0)
    // TMOD: T0_GATE=0, T0_C/T=0, T0_M1=0, T0_M0=0 (模式0)
    TMOD &amp;= 0xF0;   // 清T0控制位
    TMOD |= 0x00;   // 模式0: 16位自动重装载

    // 12T模式 (SYSclk/12)
    AUXR &amp;= ~0x80;// T0x12 = 0

    // 设置重装载值 (10ms @ 11.0592MHz)
    TH0 = 0xDC;
    TL0 = 0x00;

    // 预分频寄存器设为0 (不分频)
    TM0PS = 0x00;

    // 清定时器0中断标志
    TF0 = 0;

    // 使能定时器0中断
    ET0 = 1;

    // 启动定时器0
    TR0 = 1;
}

/*------------------------------------------------------------------*/
/* 外部中断初始化                                                   */
/*------------------------------------------------------------------*/
void EXTI_Init(void)
{
    // INT0 (P3.2) - 模式切换: 下降沿触发
    IT0 = 1;      // 下降沿触发
    EX0 = 1;      // 使能INT0中断

    // INT1 (P3.3) - 功能切换: 下降沿触发
    IT1 = 1;      // 下降沿触发
    EX1 = 1;      // 使能INT1中断
}

/*------------------------------------------------------------------*/
/* 延时函数 (基于定时器查询方式)                                     */
/*------------------------------------------------------------------*/
void Delay10ms(uint16_t ms)
{
    uint16_t i;
    for(i = 0; i &lt; ms; i++)
    {
      // 利用定时器0做10ms延时 (查询方式)
      // 这里简单用软件延时,实际项目中建议用定时器
      uint8_t j;
      for(j = 0; j &lt; 180; j++)    // 粗略10ms @ 11.0592MHz
      {
            _nop_();
      }
    }
}

/*------------------------------------------------------------------*/
/* 关闭所有灯                                                       */
/*------------------------------------------------------------------*/
void SetAllLightsOff(void)
{
    P1 = 0x00;            // 关闭所有车灯
    P2 &amp;= 0xF0;             // 关闭流水灯(仅低4位)
    g_flow_index = 0;
}

/*------------------------------------------------------------------*/
/* 灯效显示函数 (每10ms调用一次)                                     */
/*------------------------------------------------------------------*/
void Light_Show(void)
{
    static uint8_t s_counter = 0;

    s_counter++;

    switch(g_current_mode)
    {
      case MODE_ALL_OFF:
            SetAllLightsOff();
            break;
      
      case MODE_LOW_BEAM:
            // 近光灯常亮
            P1 = 0x00;
            LED_LOW = 1;
            P2 &amp;= 0xF0;   // 流水灯关闭
            break;
      
      case MODE_HIGH_BEAM:
            // 远光灯常亮
            P1 = 0x00;
            LED_HIGH = 1;
            P2 &amp;= 0xF0;
            break;
      
      case MODE_LEFT_TURN:
            // 左转向灯闪烁 (500ms周期: 250ms亮, 250ms灭)
            if(s_counter &gt;= 25)// 25 * 10ms = 250ms
            {
                s_counter = 0;
                g_blink_state = !g_blink_state;
                P1 = 0x00;
                LED_LEFT = g_blink_state;
            }
            P2 &amp;= 0xF0;
            break;
      
      case MODE_RIGHT_TURN:
            // 右转向灯闪烁
            if(s_counter &gt;= 25)
            {
                s_counter = 0;
                g_blink_state = !g_blink_state;
                P1 = 0x00;
                LED_RIGHT = g_blink_state;
            }
            P2 &amp;= 0xF0;
            break;
      
      case MODE_HAZARD:
            // 双闪 (左右同时闪烁)
            if(s_counter &gt;= 25)
            {
                s_counter = 0;
                g_blink_state = !g_blink_state;
                P1 = 0x00;
                LED_LEFT = g_blink_state;
                LED_RIGHT = g_blink_state;
            }
            P2 &amp;= 0xF0;
            break;
      
      case MODE_BRAKE:
            // 刹车灯常亮
            P1 = 0x00;
            LED_BRAKE = 1;
            P2 &amp;= 0xF0;
            break;
      
      case MODE_REVERSE:
            // 倒车灯常亮
            P1 = 0x00;
            LED_BACK = 1;
            P2 &amp;= 0xF0;
            break;
      
      case MODE_FLOW_LEFT:
            // 流水灯向左
            if(s_counter &gt;= 10)// 100ms移动一步
            {
                s_counter = 0;
                Flow_Left();
            }
            P1 = 0x00;// 车灯关闭
            break;
      
      case MODE_FLOW_RIGHT:
            // 流水灯向右
            if(s_counter &gt;= 10)
            {
                s_counter = 0;
                Flow_Right();
            }
            P1 = 0x00;
            break;
      
      case MODE_FLOW_BREATH:
            // 流水灯呼吸效果
            if(s_counter &gt;= 3)   // 30ms更新一次呼吸值
            {
                s_counter = 0;
                Flow_Breath();
            }
            P1 = 0x00;
            break;
      
      default:
            break;
    }
}

/*------------------------------------------------------------------*/
/* 流水灯向左移动                                                    */
/* 效果: LED从右向左依次点亮 (P2.3→P2.2→P2.1→P2.0)                */
/*------------------------------------------------------------------*/
void Flow_Left(void)
{
    // 先全部关闭
    P2 &amp;= 0xF0;

    // 根据索引点亮对应LED
    switch(g_flow_index)
    {
      case 0: P20 = 1; break;   // 最右
      case 1: P21 = 1; break;
      case 2: P22 = 1; break;
      case 3: P23 = 1; break;   // 最左
      default: break;
    }

    g_flow_index++;
    if(g_flow_index &gt;= 4)
    {
      g_flow_index = 0;
    }
}

/*------------------------------------------------------------------*/
/* 流水灯向右移动                                                    */
/* 效果: LED从左向右依次点亮 (P2.0→P2.1→P2.2→P2.3)                */
/*------------------------------------------------------------------*/
void Flow_Right(void)
{
    // 先全部关闭
    P2 &amp;= 0xF0;

    // 根据索引点亮对应LED
    switch(g_flow_index)
    {
      case 0: P23 = 1; break;   // 最左
      case 1: P22 = 1; break;
      case 2: P21 = 1; break;
      case 3: P20 = 1; break;   // 最右
      default: break;
    }

    g_flow_index++;
    if(g_flow_index &gt;= 4)
    {
      g_flow_index = 0;
    }
}

/*------------------------------------------------------------------*/
/* 流水灯呼吸效果                                                    */
/* 效果: 4个LED依次点亮→全亮→依次熄灭→全灭, 循环                   */
/*------------------------------------------------------------------*/
void Flow_Breath(void)
{
    uint8_t i;

    // 先全部关闭
    P2 &amp;= 0xF0;

    // 根据呼吸值决定点亮几个灯
    // breath_value: 0~100, 映射到0~4个灯
    uint8_t num_leds = (g_breath_value * 4) / 100;

    // 点亮对应数量的灯 (从P2.0开始)
    for(i = 0; i &lt; num_leds; i++)
    {
      switch(i)
      {
            case 0: P20 = 1; break;
            case 1: P21 = 1; break;
            case 2: P22 = 1; break;
            case 3: P23 = 1; break;
            default: break;
      }
    }

    // 更新呼吸值
    if(g_breath_dir == 0)// 增加
    {
      g_breath_value += 2;
      if(g_breath_value &gt;= 100)
      {
            g_breath_value = 100;
            g_breath_dir = 1;// 转为减少
      }
    }
    else// 减少
    {
      if(g_breath_value &gt;= 2)
            g_breath_value -= 2;
      else
            g_breath_value = 0;
   
      if(g_breath_value &lt;= 0)
      {
            g_breath_value = 0;
            g_breath_dir = 0;// 转为增加
      }
    }
}

/*------------------------------------------------------------------*/
/* 定时器0中断服务函数 (10ms)                                        */
/*------------------------------------------------------------------*/
void Timer0_ISR(void) interrupt 1
{
    // 重装载由硬件自动完成 (16位自动重装载模式)
    g_tick_10ms++;
}

/*------------------------------------------------------------------*/
/* 外部中断0 - 模式切换按键 (P3.2/INT0)                              */
/* 每按一次切换到下一个灯效模式                                     */
/*------------------------------------------------------------------*/
void EXTI0_ISR(void) interrupt 0
{
    // 简单消抖 (等待10ms)
    uint8_t i;
    for(i = 0; i &lt; 10; i++)
    {
      // 空转延时
      _nop_(); _nop_(); _nop_(); _nop_();
    }

    // 再次确认按键按下
    if(KEY_MODE == 0)
    {
      // 切换到下一个模式
      g_current_mode++;
      if(g_current_mode &gt;= MODE_MAX)
      {
            g_current_mode = MODE_ALL_OFF;
      }
   
      // 重置相关状态变量
      g_blink_state = 0;
      g_flow_index = 0;
      g_breath_value = 0;
      g_breath_dir = 0;
   
      // 先关闭所有灯
      SetAllLightsOff();
    }
}

/*------------------------------------------------------------------*/
/* 外部中断1 - 功能切换按键 (P3.3/INT1)                              */
/* 在流水灯模式下切换方向                                           */
/*------------------------------------------------------------------*/
void EXTI1_ISR(void) interrupt 2
{
    // 简单消抖
    uint8_t i;
    for(i = 0; i &lt; 10; i++)
    {
      _nop_(); _nop_(); _nop_(); _nop_();
    }

    // 再次确认按键按下
    if(KEY_SWITCH == 0)
    {
      // 如果在流水灯模式下,切换方向
      if(g_current_mode == MODE_FLOW_LEFT)
      {
            g_current_mode = MODE_FLOW_RIGHT;
      }
      else if(g_current_mode == MODE_FLOW_RIGHT)
      {
            g_current_mode = MODE_FLOW_LEFT;
      }
      // 其他模式下可扩展其他功能
   
      g_flow_index = 0;
      SetAllLightsOff();
    }
}

/*------------------------------------------------------------------*/
/* 结束                                                             */
/*------------------------------------------------------------------*/
</code></pre>
<hr />
<h3>程序功能说明</h3>
<table>
<thead>
<tr>
<th align="center">模式</th>
<th align="center">按键操作</th>
<th align="left">效果描述</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><strong>0 - 全部关闭</strong></td>
<td align="center">上电默认</td>
<td align="left">所有LED熄灭</td>
</tr>
<tr>
<td align="center"><strong>1 - 近光灯</strong></td>
<td align="center">按MODE键1次</td>
<td align="left">P1.4常亮</td>
</tr>
<tr>
<td align="center"><strong>2 - 远光灯</strong></td>
<td align="center">按MODE键2次</td>
<td align="left">P1.5常亮</td>
</tr>
<tr>
<td align="center"><strong>3 - 左转向灯</strong></td>
<td align="center">按MODE键3次</td>
<td align="left">P1.0以500ms周期闪烁</td>
</tr>
<tr>
<td align="center"><strong>4 - 右转向灯</strong></td>
<td align="center">按MODE键4次</td>
<td align="left">P1.1以500ms周期闪烁</td>
</tr>
<tr>
<td align="center"><strong>5 - 双闪</strong></td>
<td align="center">按MODE键5次</td>
<td align="left">P1.0和P1.1同时闪烁</td>
</tr>
<tr>
<td align="center"><strong>6 - 刹车灯</strong></td>
<td align="center">按MODE键6次</td>
<td align="left">P1.6常亮</td>
</tr>
<tr>
<td align="center"><strong>7 - 倒车灯</strong></td>
<td align="center">按MODE键7次</td>
<td align="left">P1.7常亮</td>
</tr>
<tr>
<td align="center"><strong>8 - 流水左移</strong></td>
<td align="center">按MODE键8次</td>
<td align="left">P2.0→P2.1→P2.2→P2.3循环</td>
</tr>
<tr>
<td align="center"><strong>9 - 流水右移</strong></td>
<td align="center">按MODE键9次</td>
<td align="left">P2.3→P2.2→P2.1→P2.0循环</td>
</tr>
<tr>
<td align="center"><strong>10 - 呼吸效果</strong></td>
<td align="center">按MODE键10次</td>
<td align="left">流水灯逐级点亮→全亮→逐级熄灭循环</td>
</tr>
<tr>
<td align="center"><strong>切换方向</strong></td>
<td align="center">按SWITCH键</td>
<td align="left">在流水左移/右移之间切换</td>
</tr>
</tbody>
</table>
<h3>使用说明</h3>
<ol>
<li><strong>MODE键 (P3.2)</strong>:每按一次切换到下一个灯效模式,共11种模式循环</li>
<li><strong>SWITCH键 (P3.3)</strong>:在流水灯模式下切换左移/右移方向</li>
<li>使用 <strong>STC-ISP 下载软件</strong> 将程序下载到芯片,内部IRC时钟设置为 <strong>11.0592MHz</strong></li>
<li>头文件使用手册中定义的 <code>ai8h.h</code>(见手册&quot;附录O Ai8H系列头文件定义&quot;)</li>
</ol>
<h3>注意事项</h3>
<ul>
<li>根据手册&quot;12 I/O口&quot;章节,除P3.0/P3.1外,其余I/O上电后为高阻输入,必须在初始化时配置为推挽输出才能驱动LED</li>
<li>推挽输出驱动LED时需串联限流电阻(参考手册:推挽输出可达20mA)</li>
<li>按键使用了内部4K上拉电阻(通过 <code>P3PU</code> 寄存器使能),外部可不加上拉</li>
<li>定时器0配置为24位自动重装载模式(手册&quot;16 定时器/计数器&quot;章节),预分频寄存器 <code>TM0PS</code> 设为0</li>
</ul>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=AI8H2K32U-45I-LQFP32%20%E8%BD%A6%E7%81%AF%E5%88%87%E6%8D%A2%20%E6%B5%81%E6%B0%B4%E7%81%AF%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;AI8H2K32U-45I-LQFP32 车灯切换 流水灯 演示程序&quot;</a></p>
<p><strong>? 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=23300&amp;highlight=%E5%B8%AEAI8H2K32U-45I-LQFP32%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%E7%94%A8%E9%80%94%EF%BC%9A%0A%E8%BD%A6%E7%81%AF%E7%9A%84%E5%88%87%E6%8D%A2%E5%92%8C%E6%B5%81%E6%B0%B4&amp;page=1&amp;extra=#pid213415">STC单片机在智能猫砂盆里面的应用 AI8H2K32U-45I-LQFP32</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24648&amp;highlight=%E5%B8%AEAI8H2K32U-45I-LQFP32%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%E7%94%A8%E9%80%94%EF%BC%9A%0A%E8%BD%A6%E7%81%AF%E7%9A%84%E5%88%87%E6%8D%A2%E5%92%8C%E6%B5%81%E6%B0%B4&amp;page=1&amp;extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=18480&amp;highlight=%E5%B8%AEAI8H2K32U-45I-LQFP32%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%E7%94%A8%E9%80%94%EF%BC%9A%0A%E8%BD%A6%E7%81%AF%E7%9A%84%E5%88%87%E6%8D%A2%E5%92%8C%E6%B5%81%E6%B0%B4&amp;page=1&amp;extra=#pid171000">在 STCMCU商铺买了一个USB转双串口的下载器,不能烧录不知怎么回事、</a></li>
</ol>
<p>⏱️ 查找用时 11.4s | 总回答用时 51.8s</p>

芯远航 发表于 2026-7-2 09:24:15

帮忙给STC32G12K128写一份完整演示程序,实现如下需求:
驱动I8080接口TFT彩屏,完成字符、数字、简易线条绘图显示


芯远航 发表于 2026-7-2 09:24:30

<p>现在我已经收集了足够的信息。让我来编写一份完整的演示程序。这份程序基于 STC32G12K128 的硬件 LCM(I8080)接口驱动 TFT 彩屏,实现字符、数字和简易线条绘图显示。</p>
<hr />
<p>以下是完整的演示程序,基于 <strong>STC32G12K128</strong> 的硬件 <strong>LCM(I8080 接口)</strong> 驱动 TFT 彩屏,实现字符、数字和简易线条绘图显示。</p>
<hr />
<h2>文件结构</h2>
<pre><code>├── main.c          — 主程序
├── lcm_driver.c    — LCM 底层驱动
├── lcm_driver.h    — LCM 驱动头文件
├── lcd_api.c       — LCD 上层绘图 API
├── lcd_api.h       — LCD API 头文件
├── font_8x16.c   — 8×16 ASCII 字库
├── font_8x16.h   — 字库头文件
└── stc32g.h      — STC32G 头文件(官方提供)
</code></pre>
<hr />
<h2>1. <code>lcm_driver.h</code> — LCM 底层驱动头文件</h2>
<pre><code class="language-c">#ifndef __LCM_DRIVER_H__
#define __LCM_DRIVER_H__

#include &quot;stc32g.h&quot;

// ========== 引脚定义(可根据实际硬件修改)==========
// 数据总线:P2 = D8~D15(高8位),P0 = D0~D7(低8位)
// 控制信号:RS(P4.5), RD(P4.4), WR(P4.2)
// 片选/复位:P1.1(CS), P1.0(RST) — 也可直接接地

sbit LCD_CS= P1^1;
sbit LCD_RST = P1^0;

// ========== LCM 寄存器地址宏 ==========
#define LCMIFCFG   (*(unsigned char volatile xdata *)0x7efe50)
#define LCMIFCFG2(*(unsigned char volatile xdata *)0x7efe51)
#define LCMIFCR    (*(unsigned char volatile xdata *)0x7efe52)
#define LCMIFSTA   (*(unsigned char volatile xdata *)0x7efe53)
#define LCMIFDATL(*(unsigned char volatile xdata *)0x7efe54)
#define LCMIFDATH(*(unsigned char volatile xdata *)0x7efe55)

// ========== LCM 命令定义 ==========
#define LCM_CMD_WR_CMD   0x84   // 写命令
#define LCM_CMD_WR_DATA0x85   // 写数据
#define LCM_CMD_RD_CMD   0x86   // 读命令/状态
#define LCM_CMD_RD_DATA0x87   // 读数据

// ========== 函数声明 ==========
void LCM_Config(void);
void LCM_WriteCmd(unsigned char cmd);
void LCM_WriteData(unsigned char dat);
void LCM_WriteData16(unsigned short dat);
void LCM_WriteData16_Bulk(unsigned short *buf, unsigned short len);

#endif
</code></pre>
<hr />
<h2>2. <code>lcm_driver.c</code> — LCM 底层驱动实现</h2>
<pre><code class="language-c">#include &quot;lcm_driver.h&quot;

//------------------------------------------------------------------------
// 函数: LCM_Config
// 描述: 配置 STC32G12K128 的 LCM 接口为 I8080 16位模式
//       数据总线: P2=高8位, P0=低8位
//       控制脚:   RS(P4.5), RD(P4.4), WR(P4.2)
// 来源: 参考 STC32G 手册 &quot;TFT彩屏接口接口(8/16位I8080/M6800接口)&quot; 章节
//------------------------------------------------------------------------
void LCM_Config(void)
{
    // 1. 使能访问 XFR 扩展寄存器
    //    手册 &quot;特殊功能寄存器(SFR、XFR),EAXFR允许访问XFR&quot; 章节:
    //    访问 XFR 前必须将 EAXFR(P_SW2.7) 置1
    P_SW2 |= 0x80;   // EAXFR = 1

    // 2. 配置 I/O 口模式
    //    P0、P2 作为数据总线 → 推挽输出
    //    P4.5(RS)、P4.4(RD)、P4.2(WR) → 推挽输出
    //    P1.0(RST)、P1.1(CS) → 推挽输出
    //    手册 &quot;I/O口&quot; 章节:PnM1=0, PnM0=1 为推挽输出
    P0M0 = 0xFF;P0M1 = 0x00;   // P0 推挽
    P2M0 = 0xFF;P2M1 = 0x00;   // P2 推挽
    P4M0 = 0x34;P4M1 = 0x00;   // P4.5(RS), P4.4(RD), P4.2(WR) 推挽
    P1M0 = 0x03;P1M1 = 0x00;   // P1.0(RST), P1.1(CS) 推挽

    // 3. 配置 LCMIFCFG (地址 7EFE50H)
    //    Bit LCMIFIE= 0(查询方式,不使能中断)
    //    Bit LCMIFIP = 00 (中断优先级,查询方式不用)
    //    Bit LCMIFDPS = 00 (16位时 P2=高8位, P0=低8位)
    //    Bit D16_D8   = 1(16位数据宽度)
    //    Bit M68_I80= 0(I8080 模式)
    //    手册 &quot;LCM接口配置寄存器(LCMIFCFG)&quot; 章节
    LCMIFCFG = 0x02;   // 0000 0010

    // 4. 配置 LCMIFCFG2 (地址 7EFE51H)
    //    Bit LCMIFCPS = 00 (RS=P4.5, RD=P4.4, WR=P4.2)
    //    Bit SETUPT   = 001 (建立时间 = (1+1)=2 个系统时钟)
    //    Bit HOLDT    = 01 (保持时间 = (1+1)=2 个系统时钟)
    //    手册 &quot;LCM接口配置寄存器2(LCMIFCFG2)&quot; 章节
    LCMIFCFG2 = 0x09;// 0000 1001

    // 5. 清除中断标志
    LCMIFSTA = 0x00;

    // 6. 使能 LCM 接口
    //    手册 &quot;LCM接口控制寄存器(LCMIFCR)&quot; 章节
    //    Bit ENLCMIF = 1 使能
    LCMIFCR = 0x80;    // 1000 0000

    // 7. 硬件复位 TFT(低电平复位,脉宽 &gt;10μs)
    LCD_RST = 0;
    // 延时约 10ms(简单循环延时,实际可用定时器)
    { unsigned int i; for(i=0; i&lt;50000; i++); }
    LCD_RST = 1;
    { unsigned int i; for(i=0; i&lt;50000; i++); }
}

//------------------------------------------------------------------------
// 函数: LCM_WriteCmd
// 描述: 通过 LCM 接口向 TFT 写入一个命令字节
// 参数: cmd — 命令字节
// 来源: 手册 &quot;LCM接口控制寄存器(LCMIFCR)&quot; 章节
//       CMD=100 为写命令
//------------------------------------------------------------------------
void LCM_WriteCmd(unsigned char cmd)
{
    LCMIFDATH = 0;          // 高字节清零(16位模式下高8位为0)
    LCMIFDATL = cmd;      // 低字节写入命令
    LCD_CS = 0;             // 片选使能
    LCMIFCR = LCM_CMD_WR_CMD;// 触发写命令脉冲
    while (!(LCMIFSTA &amp; 0x01)); // 等待传输完成
    LCMIFSTA = 0x00;      // 清除完成标志
    LCD_CS = 1;             // 片选禁止
}

//------------------------------------------------------------------------
// 函数: LCM_WriteData
// 描述: 通过 LCM 接口向 TFT 写入一个数据字节
// 参数: dat — 数据字节
// 来源: 手册 &quot;LCM接口控制寄存器(LCMIFCR)&quot; 章节
//       CMD=101 为写数据
//------------------------------------------------------------------------
void LCM_WriteData(unsigned char dat)
{
    LCMIFDATH = 0;
    LCMIFDATL = dat;
    LCD_CS = 0;
    LCMIFCR = LCM_CMD_WR_DATA;// 触发写数据脉冲
    while (!(LCMIFSTA &amp; 0x01));
    LCMIFSTA = 0x00;
    LCD_CS = 1;
}

//------------------------------------------------------------------------
// 函数: LCM_WriteData16
// 描述: 通过 LCM 接口向 TFT 写入一个 16 位颜色数据
// 参数: dat — 16 位颜色值 (R5G6B5)
//------------------------------------------------------------------------
void LCM_WriteData16(unsigned short dat)
{
    LCMIFDATH = (unsigned char)(dat &gt;&gt; 8);   // 高8位
    LCMIFDATL = (unsigned char)(dat &amp; 0xFF); // 低8位
    LCD_CS = 0;
    LCMIFCR = LCM_CMD_WR_DATA;
    while (!(LCMIFSTA &amp; 0x01));
    LCMIFSTA = 0x00;
    LCD_CS = 1;
}

//------------------------------------------------------------------------
// 函数: LCM_WriteData16_Bulk
// 描述: 批量写入 16 位颜色数据(用于快速刷屏/填充)
// 参数: buf — 数据缓冲区指针, len — 数据个数
//------------------------------------------------------------------------
void LCM_WriteData16_Bulk(unsigned short *buf, unsigned short len)
{
    unsigned short i;
    LCD_CS = 0;
    for (i = 0; i &lt; len; i++)
    {
      LCMIFDATH = (unsigned char)(buf &gt;&gt; 8);
      LCMIFDATL = (unsigned char)(buf &amp; 0xFF);
      LCMIFCR = LCM_CMD_WR_DATA;
      while (!(LCMIFSTA &amp; 0x01));
      LCMIFSTA = 0x00;
    }
    LCD_CS = 1;
}
</code></pre>
<hr />
<h2>3. <code>lcd_api.h</code> — LCD 上层 API 头文件</h2>
<pre><code class="language-c">#ifndef __LCD_API_H__
#define __LCD_API_H__

#include &quot;stc32g.h&quot;

// ========== 屏幕尺寸(以 ILI9341/ST7789 等 320×240 为例)==========
#define LCD_WIDTH   320
#define LCD_HEIGHT240

// ========== 常用颜色定义 (RGB565) ==========
#define COLOR_BLACK   0x0000
#define COLOR_WHITE   0xFFFF
#define COLOR_RED       0xF800
#define COLOR_GREEN   0x07E0
#define COLOR_BLUE      0x001F
#define COLOR_YELLOW    0xFFE0
#define COLOR_CYAN      0x07FF
#define COLOR_MAGENTA   0xF81F
#define COLOR_GRAY      0x8410
#define COLOR_ORANGE    0xFD20

// ========== 函数声明 ==========
void LCD_Init(void);
void LCD_SetWindow(unsigned short x1, unsigned short y1,
                   unsigned short x2, unsigned short y2);
void LCD_Fill(unsigned short x1, unsigned short y1,
            unsigned short x2, unsigned short y2,
            unsigned short color);
void LCD_Clear(unsigned short color);
void LCD_DrawPoint(unsigned short x, unsigned short y,
                   unsigned short color);
void LCD_DrawLine(unsigned short x1, unsigned short y1,
                  unsigned short x2, unsigned short y2,
                  unsigned short color);
void LCD_DrawRect(unsigned short x1, unsigned short y1,
                  unsigned short x2, unsigned short y2,
                  unsigned short color);
void LCD_DrawCircle(unsigned short x0, unsigned short y0,
                  unsigned short r, unsigned short color);
void LCD_ShowChar(unsigned short x, unsigned short y,
                  char ch, unsigned short fc, unsigned short bc);
void LCD_ShowString(unsigned short x, unsigned short y,
                  const char *str,
                  unsigned short fc, unsigned short bc);
void LCD_ShowNum(unsigned short x, unsigned short y,
               unsigned long num, unsigned char len,
               unsigned short fc, unsigned short bc);

#endif
</code></pre>
<hr />
<h2>4. <code>lcd_api.c</code> — LCD 上层 API 实现</h2>
<pre><code class="language-c">#include &quot;lcd_api.h&quot;
#include &quot;lcm_driver.h&quot;
#include &quot;font_8x16.h&quot;

// ========== TFT 驱动 IC 命令集(以 ILI9341 为例)==========
// 不同驱动 IC 命令可能不同,请根据实际屏的 datasheet 修改
#define CMD_SWRESET   0x01
#define CMD_SLEEP_OUT   0x11
#define CMD_DISP_ON   0x29
#define CMD_CASET       0x2A   // 列地址设置
#define CMD_RASET       0x2B   // 行地址设置
#define CMD_RAMWR       0x2C   // 写 GRAM

//------------------------------------------------------------------------
// 函数: LCD_Init
// 描述: TFT 彩屏初始化(以 ILI9341 为例)
//------------------------------------------------------------------------
void LCD_Init(void)
{
    // LCM 硬件接口初始化
    LCM_Config();

    // 软件复位
    LCM_WriteCmd(CMD_SWRESET);
    { unsigned int i; for(i=0; i&lt;50000; i++); }

    // 退出睡眠模式
    LCM_WriteCmd(CMD_SLEEP_OUT);
    { unsigned int i; for(i=0; i&lt;50000; i++); }

    // 开显示
    LCM_WriteCmd(CMD_DISP_ON);
    { unsigned int i; for(i=0; i&lt;50000; i++); }

    // 清屏为黑色
    LCD_Clear(COLOR_BLACK);
}

//------------------------------------------------------------------------
// 函数: LCD_SetWindow
// 描述: 设置 GRAM 读写窗口
//------------------------------------------------------------------------
void LCD_SetWindow(unsigned short x1, unsigned short y1,
                   unsigned short x2, unsigned short y2)
{
    LCM_WriteCmd(CMD_CASET);
    LCM_WriteData16(x1 &gt;&gt; 8);   LCM_WriteData16(x1 &amp; 0xFF);
    LCM_WriteData16(x2 &gt;&gt; 8);   LCM_WriteData16(x2 &amp; 0xFF);

    LCM_WriteCmd(CMD_RASET);
    LCM_WriteData16(y1 &gt;&gt; 8);   LCM_WriteData16(y1 &amp; 0xFF);
    LCM_WriteData16(y2 &gt;&gt; 8);   LCM_WriteData16(y2 &amp; 0xFF);

    LCM_WriteCmd(CMD_RAMWR);
}

//------------------------------------------------------------------------
// 函数: LCD_Fill
// 描述: 用指定颜色填充矩形区域
//------------------------------------------------------------------------
void LCD_Fill(unsigned short x1, unsigned short y1,
            unsigned short x2, unsigned short y2,
            unsigned short color)
{
    unsigned long pixels;
    unsigned short i;

    LCD_SetWindow(x1, y1, x2, y2);

    pixels = (unsigned long)(x2 - x1 + 1) * (y2 - y1 + 1);

    LCD_CS = 0;
    for (i = 0; i &lt; (unsigned short)pixels; i++)
    {
      LCM_WriteData16(color);
    }
    LCD_CS = 1;
}

//------------------------------------------------------------------------
// 函数: LCD_Clear
// 描述: 清屏
//------------------------------------------------------------------------
void LCD_Clear(unsigned short color)
{
    LCD_Fill(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1, color);
}

//------------------------------------------------------------------------
// 函数: LCD_DrawPoint
// 描述: 画一个像素点
//------------------------------------------------------------------------
void LCD_DrawPoint(unsigned short x, unsigned short y,
                   unsigned short color)
{
    LCD_SetWindow(x, y, x, y);
    LCM_WriteData16(color);
}

//------------------------------------------------------------------------
// 函数: LCD_DrawLine
// 描述: 使用 Bresenham 算法画任意直线
//------------------------------------------------------------------------
void LCD_DrawLine(unsigned short x1, unsigned short y1,
                  unsigned short x2, unsigned short y2,
                  unsigned short color)
{
    int dx, dy, sx, sy, err, e2;

    dx = (x1 &lt; x2) ? (x2 - x1) : (x1 - x2);
    dy = (y1 &lt; y2) ? (y2 - y1) : (y1 - y2);
    sx = (x1 &lt; x2) ? 1 : -1;
    sy = (y1 &lt; y2) ? 1 : -1;
    err = dx - dy;

    while (1)
    {
      LCD_DrawPoint(x1, y1, color);
      if (x1 == x2 &amp;&amp; y1 == y2) break;
      e2 = err * 2;
      if (e2 &gt; -dy) { err -= dy; x1 += sx; }
      if (e2 &lt;dx) { err += dx; y1 += sy; }
    }
}

//------------------------------------------------------------------------
// 函数: LCD_DrawRect
// 描述: 画矩形框(空心)
//------------------------------------------------------------------------
void LCD_DrawRect(unsigned short x1, unsigned short y1,
                  unsigned short x2, unsigned short y2,
                  unsigned short color)
{
    LCD_DrawLine(x1, y1, x2, y1, color);   // 上边
    LCD_DrawLine(x1, y2, x2, y2, color);   // 下边
    LCD_DrawLine(x1, y1, x1, y2, color);   // 左边
    LCD_DrawLine(x2, y1, x2, y2, color);   // 右边
}

//------------------------------------------------------------------------
// 函数: LCD_DrawCircle
// 描述: 使用中点画圆算法画圆
//------------------------------------------------------------------------
void LCD_DrawCircle(unsigned short x0, unsigned short y0,
                  unsigned short r, unsigned short color)
{
    int x = 0, y = r;
    int d = 1 - r;

    while (x &lt;= y)
    {
      LCD_DrawPoint(x0 + x, y0 + y, color);
      LCD_DrawPoint(x0 - x, y0 + y, color);
      LCD_DrawPoint(x0 + x, y0 - y, color);
      LCD_DrawPoint(x0 - x, y0 - y, color);
      LCD_DrawPoint(x0 + y, y0 + x, color);
      LCD_DrawPoint(x0 - y, y0 + x, color);
      LCD_DrawPoint(x0 + y, y0 - x, color);
      LCD_DrawPoint(x0 - y, y0 - x, color);

      if (d &lt; 0)
            d += 2 * x + 3;
      else
      {
            d += 2 * (x - y) + 5;
            y--;
      }
      x++;
    }
}

//------------------------------------------------------------------------
// 函数: LCD_ShowChar
// 描述: 在指定位置显示一个 8×16 ASCII 字符
// 参数: x, y — 左上角坐标
//       ch   — 要显示的字符
//       fc   — 前景色(字符颜色)
//       bc   — 背景色
//------------------------------------------------------------------------
void LCD_ShowChar(unsigned short x, unsigned short y,
                  char ch, unsigned short fc, unsigned short bc)
{
    unsigned char i, j;
    unsigned char data_byte;
    unsigned short idx;

    if (ch &lt; 0x20 || ch &gt; 0x7E) ch = ' ';// 非可见字符显示为空格
    idx = (ch - 0x20) * 16;                // 字库索引

    for (i = 0; i &lt; 16; i++)
    {
      data_byte = ASCII8x16;    // 读取一行字模
      for (j = 0; j &lt; 8; j++)
      {
            if (data_byte &amp; (0x80 &gt;&gt; j))
                LCD_DrawPoint(x + j, y + i, fc);
            else
                LCD_DrawPoint(x + j, y + i, bc);
      }
    }
}

//------------------------------------------------------------------------
// 函数: LCD_ShowString
// 描述: 在指定位置显示字符串
//------------------------------------------------------------------------
void LCD_ShowString(unsigned short x, unsigned short y,
                  const char *str,
                  unsigned short fc, unsigned short bc)
{
    while (*str)
    {
      LCD_ShowChar(x, y, *str, fc, bc);
      x += 8;          // 字符宽度 8 像素
      if (x &gt; LCD_WIDTH - 8)// 自动换行
      {
            x = 0;
            y += 16;   // 字符高度 16 像素
      }
      str++;
    }
}

//------------------------------------------------------------------------
// 函数: LCD_ShowNum
// 描述: 在指定位置显示一个无符号整数
// 参数: len — 显示位数(不足前补零)
//------------------------------------------------------------------------
void LCD_ShowNum(unsigned short x, unsigned short y,
               unsigned long num, unsigned char len,
               unsigned short fc, unsigned short bc)
{
    char buf;
    unsigned char i;
    unsigned long divisor = 1;

    for (i = 1; i &lt; len; i++) divisor *= 10;

    for (i = 0; i &lt; len; i++)
    {
      buf = (char)(num / divisor) + '0';
      num %= divisor;
      divisor /= 10;
    }
    buf = '\0';

    LCD_ShowString(x, y, buf, fc, bc);
}
</code></pre>
<hr />
<h2>5. <code>font_8x16.h</code> — 8×16 ASCII 字库头文件</h2>
<pre><code class="language-c">#ifndef __FONT_8X16_H__
#define __FONT_8X16_H__

// 8×16 ASCII 字库(空格 0x20 ~ 波浪号 0x7E,共 95 个字符)
// 每个字符 16 字节,每字节对应一行 8 个像素(MSB 在左)
extern unsigned char code ASCII8x16[];

#endif
</code></pre>
<hr />
<h2>6. <code>font_8x16.c</code> — 8×16 ASCII 字库(部分示例,完整字库需包含全部 95 个字符)</h2>
<pre><code class="language-c">#include &quot;font_8x16.h&quot;

// 此处仅列出部分字符作为示例。
// 实际使用时请使用取模软件(如 PCtoLCD2002)生成完整 95 个字符的字库。
// 字模格式:逐行式,每行 8 像素(1 字节),MSB 在左。
unsigned char code ASCII8x16[] = {
    // 0x20 ' '
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
    0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,

    // 0x21 '!'
    0x00,0x00,0x18,0x18,0x18,0x18,0x18,0x18,
    0x18,0x00,0x18,0x18,0x00,0x00,0x00,0x00,

    // 0x30 '0'
    0x00,0x00,0x3C,0x66,0x66,0x66,0x66,0x66,
    0x66,0x66,0x66,0x3C,0x00,0x00,0x00,0x00,

    // 0x31 '1'
    0x00,0x00,0x18,0x38,0x78,0x18,0x18,0x18,
    0x18,0x18,0x18,0x7E,0x00,0x00,0x00,0x00,

    // 0x41 'A'
    0x00,0x00,0x3C,0x66,0x66,0x66,0x66,0x7E,
    0x66,0x66,0x66,0x66,0x00,0x00,0x00,0x00,

    // 0x42 'B'
    0x00,0x00,0x7C,0x66,0x66,0x66,0x7C,0x66,
    0x66,0x66,0x66,0x7C,0x00,0x00,0x00,0x00,

    // ... 其余字符请用取模软件生成完整 95 个字符 ...
    // 为节省篇幅,此处省略。完整字库约 95×16 = 1520 字节。
};
</code></pre>
<blockquote>
<p><strong>注意</strong>:以上字库仅列出 5 个字符作为示例。实际使用时,请用取模软件(如 PCtoLCD2002)生成完整的 95 个 ASCII 可见字符(0x20~0x7E)字库,替换上述内容。</p>
</blockquote>
<hr />
<h2>7. <code>main.c</code> — 主程序(演示字符、数字、线条绘图)</h2>
<pre><code class="language-c">#include &quot;stc32g.h&quot;
#include &quot;lcm_driver.h&quot;
#include &quot;lcd_api.h&quot;

//------------------------------------------------------------------------
// 函数: delay_ms
// 描述: 简单毫秒级延时(基于循环,实际项目建议用定时器)
//------------------------------------------------------------------------
void delay_ms(unsigned int ms)
{
    unsigned int i, j;
    for (i = 0; i &lt; ms; i++)
      for (j = 0; j &lt; 2000; j++);// 约 1ms @ 35MHz
}

//------------------------------------------------------------------------
// 函数: main
// 描述: 主程序入口
//------------------------------------------------------------------------
void main(void)
{
    // ===== 1. 系统初始化 =====
    // 手册 &quot;时钟管理&quot; 章节:设置 CPU 为最快速度
    EAXFR = 1;            // 使能访问 XFR
    CKCON = 0x00;         // 外部数据总线最快
    WTST= 0x00;         // 程序代码等待参数最快

    // ===== 2. TFT 彩屏初始化 =====
    LCD_Init();

    // ===== 3. 演示循环 =====
    while (1)
    {
      // ---- 3.1 字符显示演示 ----
      LCD_Clear(COLOR_BLACK);
      LCD_ShowString(10, 10,&quot;Hello STC32G!&quot;, COLOR_YELLOW, COLOR_BLACK);
      LCD_ShowString(10, 30,&quot;I8080 TFT Demo&quot;, COLOR_CYAN, COLOR_BLACK);
      LCD_ShowString(10, 50,&quot;STC MCU&quot;, COLOR_GREEN, COLOR_BLACK);
      delay_ms(2000);

      // ---- 3.2 数字显示演示 ----
      LCD_Clear(COLOR_BLUE);
      LCD_ShowString(10, 10,&quot;Number Display:&quot;, COLOR_WHITE, COLOR_BLUE);
      LCD_ShowString(10, 30,&quot;PI = 3.14159&quot;, COLOR_YELLOW, COLOR_BLUE);
      LCD_ShowString(10, 50,&quot;Count:&quot;, COLOR_WHITE, COLOR_BLUE);
      LCD_ShowNum(70, 50, 2024, 4, COLOR_ORANGE, COLOR_BLUE);
      LCD_ShowString(10, 70,&quot;Hex: 0x&quot;, COLOR_WHITE, COLOR_BLUE);
      LCD_ShowNum(70, 70, 0xABCD, 4, COLOR_ORANGE, COLOR_BLUE);
      delay_ms(2000);

      // ---- 3.3 线条绘图演示 ----
      LCD_Clear(COLOR_BLACK);

      // 画网格
      unsigned short i;
      for (i = 0; i &lt; LCD_WIDTH; i += 20)
            LCD_DrawLine(i, 0, i, LCD_HEIGHT - 1, COLOR_GRAY);
      for (i = 0; i &lt; LCD_HEIGHT; i += 20)
            LCD_DrawLine(0, i, LCD_WIDTH - 1, i, COLOR_GRAY);

      // 画彩色直线
      LCD_DrawLine(0, 0, LCD_WIDTH - 1, LCD_HEIGHT - 1, COLOR_RED);
      LCD_DrawLine(LCD_WIDTH - 1, 0, 0, LCD_HEIGHT - 1, COLOR_GREEN);
      LCD_DrawLine(LCD_WIDTH / 2, 0, LCD_WIDTH / 2, LCD_HEIGHT - 1, COLOR_YELLOW);
      LCD_DrawLine(0, LCD_HEIGHT / 2, LCD_WIDTH - 1, LCD_HEIGHT / 2, COLOR_CYAN);

      // 画矩形框
      LCD_DrawRect(20, 20, LCD_WIDTH - 20, LCD_HEIGHT - 20, COLOR_ORANGE);
      LCD_DrawRect(40, 40, LCD_WIDTH - 40, LCD_HEIGHT - 40, COLOR_MAGENTA);

      // 画圆
      LCD_DrawCircle(LCD_WIDTH / 2, LCD_HEIGHT / 2, 60, COLOR_RED);
      LCD_DrawCircle(LCD_WIDTH / 2, LCD_HEIGHT / 2, 40, COLOR_GREEN);
      LCD_DrawCircle(LCD_WIDTH / 2, LCD_HEIGHT / 2, 20, COLOR_BLUE);

      // 显示文字说明
      LCD_ShowString(10, LCD_HEIGHT - 20, &quot;Lines &amp; Circles Demo&quot;, COLOR_WHITE, COLOR_BLACK);
      delay_ms(3000);

      // ---- 3.4 颜色填充演示 ----
      LCD_Fill(0, 0, LCD_WIDTH / 2 - 1, LCD_HEIGHT / 2 - 1, COLOR_RED);
      LCD_Fill(LCD_WIDTH / 2, 0, LCD_WIDTH - 1, LCD_HEIGHT / 2 - 1, COLOR_GREEN);
      LCD_Fill(0, LCD_HEIGHT / 2, LCD_WIDTH / 2 - 1, LCD_HEIGHT - 1, COLOR_BLUE);
      LCD_Fill(LCD_WIDTH / 2, LCD_HEIGHT / 2, LCD_WIDTH - 1, LCD_HEIGHT - 1, COLOR_YELLOW);
      LCD_ShowString(LCD_WIDTH / 2 - 40, LCD_HEIGHT / 2 - 8,
                     &quot;RGBY&quot;, COLOR_WHITE, COLOR_BLACK);
      delay_ms(2000);
    }
}
</code></pre>
<hr />
<h2>使用说明</h2>
<h3>1. 硬件连接</h3>
<table>
<thead>
<tr>
<th align="center">STC32G12K128 引脚</th>
<th align="center">TFT 彩屏引脚</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">P0.0~P0.7</td>
<td align="center">D0~D7</td>
<td align="left">数据总线低 8 位</td>
</tr>
<tr>
<td align="center">P2.0~P2.7</td>
<td align="center">D8~D15</td>
<td align="left">数据总线高 8 位</td>
</tr>
<tr>
<td align="center">P4.5</td>
<td align="center">RS (DC)</td>
<td align="left">命令/数据选择</td>
</tr>
<tr>
<td align="center">P4.4</td>
<td align="center">RD</td>
<td align="left">读信号</td>
</tr>
<tr>
<td align="center">P4.2</td>
<td align="center">WR</td>
<td align="left">写信号</td>
</tr>
<tr>
<td align="center">P1.0</td>
<td align="center">RST</td>
<td align="left">复位(低电平复位)</td>
</tr>
<tr>
<td align="center">P1.1</td>
<td align="center">CS</td>
<td align="left">片选(低电平有效)</td>
</tr>
<tr>
<td align="center">VCC</td>
<td align="center">VCC</td>
<td align="left">3.3V 或 5V(视屏规格)</td>
</tr>
<tr>
<td align="center">GND</td>
<td align="center">GND</td>
<td align="left">地</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注意</strong>:以上引脚分配对应手册中 <code>LCMIFCPS=00</code> 的默认配置(RS=P4.5, RD=P4.4, WR=P4.2),数据总线为 <code>LCMIFDPS=00</code> 的 16 位模式(P2=高8位, P0=低8位)。如需更改,请参考手册 &quot;LCM接口功能脚切换&quot; 章节修改 <code>LCMIFCFG</code> 和 <code>LCMIFCFG2</code> 寄存器。</p>
</blockquote>
<h3>2. 软件配置</h3>
<ul>
<li><strong>TFT 驱动 IC</strong>:本程序以 ILI9341 为例(320×240)。若使用其他驱动 IC(如 ST7789、ST7735、ST7796 等),请根据其 datasheet 修改 <code>lcd_api.c</code> 中的初始化命令序列。</li>
<li><strong>字库</strong>:<code>font_8x16.c</code> 中仅列出 5 个字符示例,请用取模软件生成完整的 95 个 ASCII 字符(8×16 点阵,逐行式,MSB 左对齐)替换。</li>
<li><strong>时钟频率</strong>:程序默认使用 ISP 下载时设置的内部 IRC 频率。如需更改,参考手册 &quot;时钟管理&quot; 章节修改 <code>CLKSEL</code> 和 <code>CLKDIV</code>。</li>
</ul>
<h3>3. 编译与下载</h3>
<ul>
<li>使用 Keil C251 编译器,选择芯片型号 <strong>STC32G12K128</strong>。</li>
<li>将上述所有源文件加入工程。</li>
<li>在 ISP 下载软件中选择合适的内部 IRC 频率(建议 35MHz 或 40MHz)。</li>
<li>下载运行。</li>
</ul>
<h3>4. 演示效果</h3>
<p>程序上电后循环演示以下内容:</p>
<ol>
<li><strong>字符显示</strong>:显示 &quot;Hello STC32G!&quot; 等字符串</li>
<li><strong>数字显示</strong>:显示整数、十六进制数</li>
<li><strong>线条绘图</strong>:网格、对角线、矩形框、圆形</li>
<li><strong>颜色填充</strong>:四象限 RGBY 色块填充</li>
</ol>
<hr />
<p>以上程序基于 STC32G 手册 &quot;TFT彩屏接口接口(8/16位I8080/M6800接口)&quot; 章节的寄存器定义和 &quot;I/O口&quot; 章节的端口配置编写,LCM 接口的查询方式参考了论坛用户的实际调试经验(帖子 TID 19555、23398)。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC32G12K128%20I8080%20%E5%BD%A9%E5%B1%8F%20%E5%AD%97%E7%AC%A6%E6%98%BE%E7%A4%BA%20%E7%BB%98%E5%9B%BE&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC32G12K128 I8080 彩屏 字符显示 绘图&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24057&amp;highlight=%E5%B8%AE%E5%BF%99%E7%BB%99STC32G12K128%E5%86%99%E4%B8%80%E4%BB%BD%E5%AE%8C%E6%95%B4%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E5%AE%9E%E7%8E%B0%E5%A6%82%E4%B8%8B%E9%9C%80%E6%B1%82%EF%BC%9A%0A%E9%A9%B1%E5%8A%A8I8080%E6%8E%A5%E5%8F%A3TFT%E5%BD%A9%E5%B1%8F%EF%BC%8C%E5%AE%8C%E6%88%90%E5%AD%97%E7%AC%A6%E3%80%81%E6%95%B0%E5%AD%97%E3%80%81%E7%AE%80%E6%98%93%E7%BA%BF%E6%9D%A1%E7%BB%98%E5%9B%BE%E6%98%BE%E7%A4%BA&amp;page=64&amp;extra=#pid222103">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=20295&amp;highlight=%E5%B8%AE%E5%BF%99%E7%BB%99STC32G12K128%E5%86%99%E4%B8%80%E4%BB%BD%E5%AE%8C%E6%95%B4%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E5%AE%9E%E7%8E%B0%E5%A6%82%E4%B8%8B%E9%9C%80%E6%B1%82%EF%BC%9A%0A%E9%A9%B1%E5%8A%A8I8080%E6%8E%A5%E5%8F%A3TFT%E5%BD%A9%E5%B1%8F%EF%BC%8C%E5%AE%8C%E6%88%90%E5%AD%97%E7%AC%A6%E3%80%81%E6%95%B0%E5%AD%97%E3%80%81%E7%AE%80%E6%98%93%E7%BA%BF%E6%9D%A1%E7%BB%98%E5%9B%BE%E6%98%BE%E7%A4%BA&amp;page=1&amp;extra=#pid188013">【AICube驱动SPI接口的 OLED12864】基于 AI32G12K128屠龙刀板载接口的 OLED屏幕驱动</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=8725&amp;highlight=%E5%B8%AE%E5%BF%99%E7%BB%99STC32G12K128%E5%86%99%E4%B8%80%E4%BB%BD%E5%AE%8C%E6%95%B4%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E5%AE%9E%E7%8E%B0%E5%A6%82%E4%B8%8B%E9%9C%80%E6%B1%82%EF%BC%9A%0A%E9%A9%B1%E5%8A%A8I8080%E6%8E%A5%E5%8F%A3TFT%E5%BD%A9%E5%B1%8F%EF%BC%8C%E5%AE%8C%E6%88%90%E5%AD%97%E7%AC%A6%E3%80%81%E6%95%B0%E5%AD%97%E3%80%81%E7%AE%80%E6%98%93%E7%BA%BF%E6%9D%A1%E7%BB%98%E5%9B%BE%E6%98%BE%E7%A4%BA&amp;page=1&amp;extra=#pid83203">基于 32G12K128实验箱V9.62_实验之TFT彩屏驱动, ILI9341</a></li>
</ol>
<p>⏱️ 查找用时 13.8s | 总回答用时 72.5s</p>

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

STC【AI 助手】帮 STC8G1K08 自动写一个完整的演示程序,要包含下面这些
1.综合例程:串口指令开启 / 关闭 ADC 采样
2.串口修改 PWM 输出频率
3.定时自动上报 2 路输入信号测量值


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