<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 -> P2.0/P2.1 (U相)
* PWM2P/PWM2N -> P2.2/P2.3 (V相)
* PWM3P/PWM3N -> P2.4/P2.5 (W相)
* ADC0(P1.0) -> U相电流
* ADC1(P1.1) -> V相电流
* ADC3(P1.3) -> W相电流
* 更新事件(计数器归零)触发ADC同步采样
******************************************************************************/
#include "stc32g.h"
#include "intrins.h"
#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 < ms; i++)
for(j = 0; j < 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 => 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 -> PWM1P=P2.0, PWM1N=P2.1
// C2PS=01 -> PWM2P=P2.2, PWM2N=P2.3
// C3PS=01 -> 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 >> 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) >> 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) >> 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) >> 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 -> FADC = SYSclk/2/16 = 24MHz/2/16 = 750kHz
// RESFMT=0 -> 左对齐 (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 << 8) | ADC_RESL;
// 根据当前通道保存结果
ch = ADC_CONTR & 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章"16位高级PWM定时器")</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章"ADC模数转换")</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模块,本程序采用"每次PWM触发采一个通道,中断中切换"的方式,三个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&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC32G12K128 PWM互补输出 三相PWM ADC触发采样"</a></p>
<p>⏱️ 查找用时 31.5s | 总回答用时 66.5s</p>
USB不停电下载的几种方式推荐?
生成对应的参考程序?
<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>"@STCISP#"</code>,用户只需在程序中调用USB库,并在ISP下载软件中做相应设置即可。</p>
<p><strong>操作步骤</strong>:</p>
<ol>
<li>在用户程序中调用STC官方USB库函数(USB-CDC虚拟串口或HID)</li>
<li>在ISP下载软件(如AIapp-ISP)中,找到 <strong>"收到用户命令后复位到ISP监控程序区"</strong> 选项</li>
<li>设置自定义命令为 <code>@STCISP#</code></li>
<li>勾选 <strong>"每次下载前都先发送自定义命令"</strong></li>
</ol>
<p><strong>参考代码(C语言,使用USB-CDC库)</strong>:</p>
<pre><code class="language-c">// 使用STC官方USB库,只需在主程序中初始化USB即可
// USB库内部已处理 @STCISP# 命令的检测
#include "stc8h.h"
#include "usb.h" // 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 "stc8h.h"
#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 >= 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 >> 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 "stc8h.h"
#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 >= 16)
{
IspProgram();// ★ 跳转到ISP下载模块,实现不停电下载
}
}
else
{
cnt7f = 0;
}
RI = 0;
}
}
void main()
{
P_SW2 |= 0x80;
SCON = 0x50;
AUXR = 0x40;
TH1 = BAUD >> 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&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "USB不停电下载的几种方式推荐?<br />
生成对应的参考程序?"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=24057&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&page=63&extra=#pid222097">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=21356&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&page=1&extra=#pid196995">Ai8051U学习打卡之旅</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=21766&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&page=1&extra=#pid200714">AI8051U实验箱学习记录</a></li>
</ol>
<p>⏱️ 查找用时 10.9s | 总回答用时 32.9s</p>
请基于 STC8H8K64U 编写完整演示程序,需包含以下功能:
[*]串口中断接收上位机指令,触发多通道 ADC 循环采集,采集电压值通过串口定时上传;
[*]两组互补硬件 PWM 输出,串口下发数值动态调整占空比,控制直流电机正反转调速;
[*]上电读取片内 EEPROM 保存的电机转速参数,运行中修改转速后自动存入 EEPROM 实现掉电记忆;
[*]附加硬件比较器做过流检测,异常时自动锁死 PWM 并串口上报故障。
<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 "stc8h.h"
#include "intrins.h"
#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 *)&temp_speed, 2);
if((temp_speed == 0) || (temp_speed > 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("STC8H8K64U Demo Start!\r\n");
UART1_SendString("Motor Speed from EEPROM: ");
UART1_PrintHex(Motor_Speed);
UART1_SendString("\r\n");
/* ---- 主循环 ---- */
while(1)
{
/* ---- 串口指令解析 ---- */
if(B_RX1_OK)
{
B_RX1_OK = 0;
if(RX1_Cnt >= 2) // 至少2字节: 命令 + 数据
{
switch(RX1_Buffer)
{
case CMD_ADC_START: // 启动ADC循环采集
ADC_Channel = 0;
B_ADC_Complete = 0;
ADC_Start(ADC_Channel);
UART1_SendString("ADC Start\r\n");
break;
case CMD_PWM_SET: // 设置PWM占空比
PWM1_Duty = ((u16)RX1_Buffer << 8) | RX1_Buffer;
PWM2_Duty = ((u16)RX1_Buffer << 8) | RX1_Buffer;
if(PWM1_Duty > 1000) PWM1_Duty = 1000;
if(PWM2_Duty > 1000) PWM2_Duty = 1000;
PWMA_SetDuty(PWM1_Duty, PWM2_Duty);
UART1_SendString("PWM Set OK\r\n");
break;
case CMD_MOTOR_SPEED: // 设置电机转速并存入EEPROM
Motor_Speed = ((u16)RX1_Buffer << 8) | RX1_Buffer;
if(Motor_Speed > 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 *)&Motor_Speed, 2);
UART1_SendString("Speed Set & Saved: ");
UART1_PrintHex(Motor_Speed);
UART1_SendString("\r\n");
break;
case CMD_READ_EEPROM: // 读取EEPROM参数
EEPROM_Read(EEPROM_ADDR_SPEED, (u8 *)&temp_speed, 2);
UART1_SendString("EEPROM Speed: ");
UART1_PrintHex(temp_speed);
UART1_SendString("\r\n");
break;
default:
UART1_SendString("Unknown CMD\r\n");
break;
}
}
RX1_Cnt = 0;
}
/* ---- ADC采集完成,启动下一通道或上传数据 ---- */
if(B_ADC_Complete)
{
B_ADC_Complete = 0;
ADC_Result = ((u16)ADC_RES << 8) | ADC_RESL;
ADC_Channel++;
if(ADC_Channel < 4)
{
ADC_Start(ADC_Channel); // 继续下一通道
}
else
{
/* ---- 4通道采集完成,通过串口上传 ---- */
UART1_SendString("ADC: ");
for(i=0; i<4; i++)
{
UART1_PrintHex(ADC_Result);
UART1_SendString(" ");
}
UART1_SendString("\r\n");
ADC_Channel = 0; // 复位,等待下次指令
}
}
/* ---- 故障处理:锁死PWM并上报 ---- */
if(B_Fault)
{
if(!B_Fault_Reported)
{
B_Fault_Reported = 1;
PWMA_OutputDisable(); // 锁死PWM输出
UART1_SendString("FAULT: Overcurrent! PWM Locked!\r\n");
}
}
}
}
/* ================================================================ */
/* 串口1初始化 (115200) */
/* ================================================================ */
void UART1_Init(void)
{
SCON = 0x50; // 模式1, 8位数据, 允许接收
T2L = (u8)BRT1;
T2H = (u8)(BRT1 >> 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<4; i++)
{
nibble = (u8)((val >> (12 - i*4)) & 0x0F);
if(nibble < 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 < 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 & 0x0F);// 启动转换
}
/* ---- ADC中断服务函数 ---- */
void ADC_ISR(void) interrupt 5
{
ADC_CONTR &= ~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) >> 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->P1.0/P1.1, 通道2->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 > 1000) duty1 = 1000;
if(duty2 > 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 &= ~0x80; // MOE=0, 输出强制为无效电平
PWMA_ENO = 0x00; // 禁止所有PWM输出
}
/* ================================================================ */
/* 硬件比较器初始化 (过流检测) */
/* ================================================================ */
void CMP_Init(void)
{
/* 比较器正端(PIS): P3.6 (过流检测信号输入) */
/* 比较器负端(NIS): 内部BandGap电压 1.19V (作为阈值) */
/* 当P3.6电压 > 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 &= ~0x40; // 清CMPIF中断标志
B_Fault = 1; // 置故障标志
}
/* ================================================================ */
/* EEPROM操作函数 (IAP方式) */
/* ================================================================ */
/* ---- EEPROM读操作 ---- */
void EEPROM_Read(u16 addr, u8 *p, u8 len)
{
u8 i;
for(i=0; i<len; i++)
{
IAP_ADDRH = (u8)(addr >> 8);
IAP_ADDRL = (u8)(addr);
IAP_CMD = 0x01; // 读命令
IAP_CONTR = 0x80; // IAPEN=1
IAP_TPS = 11; // 11.0592MHz -> 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<len; i++)
{
IAP_ADDRH = (u8)(addr >> 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 >> 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 &= 0xF0;
TH0 = (u8)((65536UL - MAIN_Fosc / 1000) >> 8);
TL0 = (u8)(65536UL - MAIN_Fosc / 1000);
ET0 = 1;
TR0 = 1;
}
/* ---- 定时器0中断(1ms) ---- */
void Timer0_ISR(void) interrupt 1
{
/* 串口接收超时处理 */
if(RX1_TimeOut > 0)
{
if(--RX1_TimeOut == 0)
{
if(RX1_Cnt > 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>"FAULT: Overcurrent! PWM Locked!"</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&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC8H8K64U 串口中断 多通道ADC 循环采集"</a></p>
<p>⏱️ 查找用时 8.3s | 总回答用时 71.0s</p>
帮AI8H2K32U-45I-LQFP32自动写一个完整的演示程序,用途:
车灯的切换和流水
<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 "ai8h.h"
#include "intrins.h"
// 定义按键引脚
#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 >= 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 &= 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 &= 0xF0; // 清T0控制位
TMOD |= 0x00; // 模式0: 16位自动重装载
// 12T模式 (SYSclk/12)
AUXR &= ~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 < ms; i++)
{
// 利用定时器0做10ms延时 (查询方式)
// 这里简单用软件延时,实际项目中建议用定时器
uint8_t j;
for(j = 0; j < 180; j++) // 粗略10ms @ 11.0592MHz
{
_nop_();
}
}
}
/*------------------------------------------------------------------*/
/* 关闭所有灯 */
/*------------------------------------------------------------------*/
void SetAllLightsOff(void)
{
P1 = 0x00; // 关闭所有车灯
P2 &= 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 &= 0xF0; // 流水灯关闭
break;
case MODE_HIGH_BEAM:
// 远光灯常亮
P1 = 0x00;
LED_HIGH = 1;
P2 &= 0xF0;
break;
case MODE_LEFT_TURN:
// 左转向灯闪烁 (500ms周期: 250ms亮, 250ms灭)
if(s_counter >= 25)// 25 * 10ms = 250ms
{
s_counter = 0;
g_blink_state = !g_blink_state;
P1 = 0x00;
LED_LEFT = g_blink_state;
}
P2 &= 0xF0;
break;
case MODE_RIGHT_TURN:
// 右转向灯闪烁
if(s_counter >= 25)
{
s_counter = 0;
g_blink_state = !g_blink_state;
P1 = 0x00;
LED_RIGHT = g_blink_state;
}
P2 &= 0xF0;
break;
case MODE_HAZARD:
// 双闪 (左右同时闪烁)
if(s_counter >= 25)
{
s_counter = 0;
g_blink_state = !g_blink_state;
P1 = 0x00;
LED_LEFT = g_blink_state;
LED_RIGHT = g_blink_state;
}
P2 &= 0xF0;
break;
case MODE_BRAKE:
// 刹车灯常亮
P1 = 0x00;
LED_BRAKE = 1;
P2 &= 0xF0;
break;
case MODE_REVERSE:
// 倒车灯常亮
P1 = 0x00;
LED_BACK = 1;
P2 &= 0xF0;
break;
case MODE_FLOW_LEFT:
// 流水灯向左
if(s_counter >= 10)// 100ms移动一步
{
s_counter = 0;
Flow_Left();
}
P1 = 0x00;// 车灯关闭
break;
case MODE_FLOW_RIGHT:
// 流水灯向右
if(s_counter >= 10)
{
s_counter = 0;
Flow_Right();
}
P1 = 0x00;
break;
case MODE_FLOW_BREATH:
// 流水灯呼吸效果
if(s_counter >= 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 &= 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 >= 4)
{
g_flow_index = 0;
}
}
/*------------------------------------------------------------------*/
/* 流水灯向右移动 */
/* 效果: LED从左向右依次点亮 (P2.0→P2.1→P2.2→P2.3) */
/*------------------------------------------------------------------*/
void Flow_Right(void)
{
// 先全部关闭
P2 &= 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 >= 4)
{
g_flow_index = 0;
}
}
/*------------------------------------------------------------------*/
/* 流水灯呼吸效果 */
/* 效果: 4个LED依次点亮→全亮→依次熄灭→全灭, 循环 */
/*------------------------------------------------------------------*/
void Flow_Breath(void)
{
uint8_t i;
// 先全部关闭
P2 &= 0xF0;
// 根据呼吸值决定点亮几个灯
// breath_value: 0~100, 映射到0~4个灯
uint8_t num_leds = (g_breath_value * 4) / 100;
// 点亮对应数量的灯 (从P2.0开始)
for(i = 0; i < 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 >= 100)
{
g_breath_value = 100;
g_breath_dir = 1;// 转为减少
}
}
else// 减少
{
if(g_breath_value >= 2)
g_breath_value -= 2;
else
g_breath_value = 0;
if(g_breath_value <= 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 < 10; i++)
{
// 空转延时
_nop_(); _nop_(); _nop_(); _nop_();
}
// 再次确认按键按下
if(KEY_MODE == 0)
{
// 切换到下一个模式
g_current_mode++;
if(g_current_mode >= 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 < 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>(见手册"附录O Ai8H系列头文件定义")</li>
</ol>
<h3>注意事项</h3>
<ul>
<li>根据手册"12 I/O口"章节,除P3.0/P3.1外,其余I/O上电后为高阻输入,必须在初始化时配置为推挽输出才能驱动LED</li>
<li>推挽输出驱动LED时需串联限流电阻(参考手册:推挽输出可达20mA)</li>
<li>按键使用了内部4K上拉电阻(通过 <code>P3PU</code> 寄存器使能),外部可不加上拉</li>
<li>定时器0配置为24位自动重装载模式(手册"16 定时器/计数器"章节),预分频寄存器 <code>TM0PS</code> 设为0</li>
</ul>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">? 搜索 "AI8H2K32U-45I-LQFP32 车灯切换 流水灯 演示程序"</a></p>
<p><strong>? 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=23300&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&page=1&extra=#pid213415">STC单片机在智能猫砂盆里面的应用 AI8H2K32U-45I-LQFP32</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=24648&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&page=1&extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=18480&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&page=1&extra=#pid171000">在 STCMCU商铺买了一个USB转双串口的下载器,不能烧录不知怎么回事、</a></li>
</ol>
<p>⏱️ 查找用时 11.4s | 总回答用时 51.8s</p>
帮忙给STC32G12K128写一份完整演示程序,实现如下需求:
驱动I8080接口TFT彩屏,完成字符、数字、简易线条绘图显示
<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 "stc32g.h"
// ========== 引脚定义(可根据实际硬件修改)==========
// 数据总线: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 "lcm_driver.h"
//------------------------------------------------------------------------
// 函数: LCM_Config
// 描述: 配置 STC32G12K128 的 LCM 接口为 I8080 16位模式
// 数据总线: P2=高8位, P0=低8位
// 控制脚: RS(P4.5), RD(P4.4), WR(P4.2)
// 来源: 参考 STC32G 手册 "TFT彩屏接口接口(8/16位I8080/M6800接口)" 章节
//------------------------------------------------------------------------
void LCM_Config(void)
{
// 1. 使能访问 XFR 扩展寄存器
// 手册 "特殊功能寄存器(SFR、XFR),EAXFR允许访问XFR" 章节:
// 访问 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) → 推挽输出
// 手册 "I/O口" 章节: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 模式)
// 手册 "LCM接口配置寄存器(LCMIFCFG)" 章节
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 个系统时钟)
// 手册 "LCM接口配置寄存器2(LCMIFCFG2)" 章节
LCMIFCFG2 = 0x09;// 0000 1001
// 5. 清除中断标志
LCMIFSTA = 0x00;
// 6. 使能 LCM 接口
// 手册 "LCM接口控制寄存器(LCMIFCR)" 章节
// Bit ENLCMIF = 1 使能
LCMIFCR = 0x80; // 1000 0000
// 7. 硬件复位 TFT(低电平复位,脉宽 >10μs)
LCD_RST = 0;
// 延时约 10ms(简单循环延时,实际可用定时器)
{ unsigned int i; for(i=0; i<50000; i++); }
LCD_RST = 1;
{ unsigned int i; for(i=0; i<50000; i++); }
}
//------------------------------------------------------------------------
// 函数: LCM_WriteCmd
// 描述: 通过 LCM 接口向 TFT 写入一个命令字节
// 参数: cmd — 命令字节
// 来源: 手册 "LCM接口控制寄存器(LCMIFCR)" 章节
// 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 & 0x01)); // 等待传输完成
LCMIFSTA = 0x00; // 清除完成标志
LCD_CS = 1; // 片选禁止
}
//------------------------------------------------------------------------
// 函数: LCM_WriteData
// 描述: 通过 LCM 接口向 TFT 写入一个数据字节
// 参数: dat — 数据字节
// 来源: 手册 "LCM接口控制寄存器(LCMIFCR)" 章节
// CMD=101 为写数据
//------------------------------------------------------------------------
void LCM_WriteData(unsigned char dat)
{
LCMIFDATH = 0;
LCMIFDATL = dat;
LCD_CS = 0;
LCMIFCR = LCM_CMD_WR_DATA;// 触发写数据脉冲
while (!(LCMIFSTA & 0x01));
LCMIFSTA = 0x00;
LCD_CS = 1;
}
//------------------------------------------------------------------------
// 函数: LCM_WriteData16
// 描述: 通过 LCM 接口向 TFT 写入一个 16 位颜色数据
// 参数: dat — 16 位颜色值 (R5G6B5)
//------------------------------------------------------------------------
void LCM_WriteData16(unsigned short dat)
{
LCMIFDATH = (unsigned char)(dat >> 8); // 高8位
LCMIFDATL = (unsigned char)(dat & 0xFF); // 低8位
LCD_CS = 0;
LCMIFCR = LCM_CMD_WR_DATA;
while (!(LCMIFSTA & 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 < len; i++)
{
LCMIFDATH = (unsigned char)(buf >> 8);
LCMIFDATL = (unsigned char)(buf & 0xFF);
LCMIFCR = LCM_CMD_WR_DATA;
while (!(LCMIFSTA & 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 "stc32g.h"
// ========== 屏幕尺寸(以 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 "lcd_api.h"
#include "lcm_driver.h"
#include "font_8x16.h"
// ========== 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<50000; i++); }
// 退出睡眠模式
LCM_WriteCmd(CMD_SLEEP_OUT);
{ unsigned int i; for(i=0; i<50000; i++); }
// 开显示
LCM_WriteCmd(CMD_DISP_ON);
{ unsigned int i; for(i=0; i<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 >> 8); LCM_WriteData16(x1 & 0xFF);
LCM_WriteData16(x2 >> 8); LCM_WriteData16(x2 & 0xFF);
LCM_WriteCmd(CMD_RASET);
LCM_WriteData16(y1 >> 8); LCM_WriteData16(y1 & 0xFF);
LCM_WriteData16(y2 >> 8); LCM_WriteData16(y2 & 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 < (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 < x2) ? (x2 - x1) : (x1 - x2);
dy = (y1 < y2) ? (y2 - y1) : (y1 - y2);
sx = (x1 < x2) ? 1 : -1;
sy = (y1 < y2) ? 1 : -1;
err = dx - dy;
while (1)
{
LCD_DrawPoint(x1, y1, color);
if (x1 == x2 && y1 == y2) break;
e2 = err * 2;
if (e2 > -dy) { err -= dy; x1 += sx; }
if (e2 <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 <= 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 < 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 < 0x20 || ch > 0x7E) ch = ' ';// 非可见字符显示为空格
idx = (ch - 0x20) * 16; // 字库索引
for (i = 0; i < 16; i++)
{
data_byte = ASCII8x16; // 读取一行字模
for (j = 0; j < 8; j++)
{
if (data_byte & (0x80 >> 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 > 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 < len; i++) divisor *= 10;
for (i = 0; i < 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 "font_8x16.h"
// 此处仅列出部分字符作为示例。
// 实际使用时请使用取模软件(如 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 "stc32g.h"
#include "lcm_driver.h"
#include "lcd_api.h"
//------------------------------------------------------------------------
// 函数: delay_ms
// 描述: 简单毫秒级延时(基于循环,实际项目建议用定时器)
//------------------------------------------------------------------------
void delay_ms(unsigned int ms)
{
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 2000; j++);// 约 1ms @ 35MHz
}
//------------------------------------------------------------------------
// 函数: main
// 描述: 主程序入口
//------------------------------------------------------------------------
void main(void)
{
// ===== 1. 系统初始化 =====
// 手册 "时钟管理" 章节:设置 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,"Hello STC32G!", COLOR_YELLOW, COLOR_BLACK);
LCD_ShowString(10, 30,"I8080 TFT Demo", COLOR_CYAN, COLOR_BLACK);
LCD_ShowString(10, 50,"STC MCU", COLOR_GREEN, COLOR_BLACK);
delay_ms(2000);
// ---- 3.2 数字显示演示 ----
LCD_Clear(COLOR_BLUE);
LCD_ShowString(10, 10,"Number Display:", COLOR_WHITE, COLOR_BLUE);
LCD_ShowString(10, 30,"PI = 3.14159", COLOR_YELLOW, COLOR_BLUE);
LCD_ShowString(10, 50,"Count:", COLOR_WHITE, COLOR_BLUE);
LCD_ShowNum(70, 50, 2024, 4, COLOR_ORANGE, COLOR_BLUE);
LCD_ShowString(10, 70,"Hex: 0x", 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 < LCD_WIDTH; i += 20)
LCD_DrawLine(i, 0, i, LCD_HEIGHT - 1, COLOR_GRAY);
for (i = 0; i < 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, "Lines & Circles Demo", 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,
"RGBY", 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位)。如需更改,请参考手册 "LCM接口功能脚切换" 章节修改 <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 频率。如需更改,参考手册 "时钟管理" 章节修改 <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>:显示 "Hello STC32G!" 等字符串</li>
<li><strong>数字显示</strong>:显示整数、十六进制数</li>
<li><strong>线条绘图</strong>:网格、对角线、矩形框、圆形</li>
<li><strong>颜色填充</strong>:四象限 RGBY 色块填充</li>
</ol>
<hr />
<p>以上程序基于 STC32G 手册 "TFT彩屏接口接口(8/16位I8080/M6800接口)" 章节的寄存器定义和 "I/O口" 章节的端口配置编写,LCM 接口的查询方式参考了论坛用户的实际调试经验(帖子 TID 19555、23398)。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC32G12K128 I8080 彩屏 字符显示 绘图"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=24057&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&page=64&extra=#pid222103">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=20295&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&page=1&extra=#pid188013">【AICube驱动SPI接口的 OLED12864】基于 AI32G12K128屠龙刀板载接口的 OLED屏幕驱动</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=8725&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&page=1&extra=#pid83203">基于 32G12K128实验箱V9.62_实验之TFT彩屏驱动, ILI9341</a></li>
</ol>
<p>⏱️ 查找用时 13.8s | 总回答用时 72.5s</p>
STC【AI 助手】帮 STC8G1K08 自动写一个完整的演示程序,要包含下面这些
1.综合例程:串口指令开启 / 关闭 ADC 采样
2.串口修改 PWM 输出频率
3.定时自动上报 2 路输入信号测量值