芯启程 发表于 2026-7-3 14:00:09

<p>好的,根据已获取的手册资料和论坛信息,我来为您整理一份 <strong>STC32G12K128 驱动 ST7789 屏</strong> 的完整程序。</p>
<hr />
<h2>STC32G12K128 驱动 ST7789 TFT 彩屏程序</h2>
<h3>说明</h3>
<p>根据 STC32G 手册(章节 <strong>&quot;29 TFT彩屏接口接口(8/16位I8080/M6800接口)&quot;</strong>),STC32G12K128 系列内置 <strong>LCM 接口</strong>,支持 I8080/M6800 并口模式。但 ST7789 驱动 IC 通常使用 <strong>SPI 串行接口</strong>(4线 SPI)通信,因此更常见的做法是使用 <strong>GPIO 模拟 SPI</strong> 或 <strong>硬件 SPI</strong> 来驱动。</p>
<p>据论坛用户反馈(帖子 TID=9254,作者 浦江***),屠龙刀-32G12K128 实验板驱动 1.3寸 240×240 ST7789 彩屏,采用 <strong>模拟 I/O 驱动 SPI</strong> 方式,方便移植。</p>
<p>以下程序基于该思路编写,使用 <strong>GPIO 模拟 SPI</strong> 驱动 ST7789,适用于 STC32G12K128。</p>
<hr />
<h3>硬件连接(屠龙刀实验板默认)</h3>
<table>
<thead>
<tr>
<th align="center">ST7789 引脚</th>
<th align="center">连接 STC32G12K128 引脚</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">CS</td>
<td align="center">P1.2</td>
</tr>
<tr>
<td align="center">DC (RS)</td>
<td align="center">P1.3</td>
</tr>
<tr>
<td align="center">SDA (MOSI)</td>
<td align="center">P1.4</td>
</tr>
<tr>
<td align="center">SCL (SCK)</td>
<td align="center">P1.5</td>
</tr>
<tr>
<td align="center">RES (RST)</td>
<td align="center">P1.6</td>
</tr>
<tr>
<td align="center">BL (背光)</td>
<td align="center">P1.7</td>
</tr>
<tr>
<td align="center">VCC</td>
<td align="center">3.3V</td>
</tr>
<tr>
<td align="center">GND</td>
<td align="center">GND</td>
</tr>
</tbody>
</table>
<blockquote>
<p>注:可根据实际硬件修改引脚定义。</p>
</blockquote>
<hr />
<h3>完整程序代码</h3>
<pre><code class="language-c">/***********************************************************
* STC32G12K128 驱动 ST7789 TFT 彩屏 (240×240)
* 接口:GPIO 模拟 SPI
* 晶振:22.1184MHz(可调整)
* 编译环境:Keil C251
***********************************************************/

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

// ==================== 引脚定义 ====================
#define TFT_CS      P12
#define TFT_DC      P13
#define TFT_SDA   P14
#define TFT_SCL   P15
#define TFT_RST   P16
#define TFT_BL      P17

#define TFT_CS_H    P12 = 1
#define TFT_CS_L    P12 = 0
#define TFT_DC_H    P13 = 1
#define TFT_DC_L    P13 = 0
#define TFT_SDA_H   P14 = 1
#define TFT_SDA_L   P14 = 0
#define TFT_SCL_H   P15 = 1
#define TFT_SCL_L   P15 = 0
#define TFT_RST_H   P16 = 1
#define TFT_RST_L   P16 = 0
#define TFT_BL_H    P17 = 1
#define TFT_BL_L    P17 = 0

// ==================== 屏幕参数 ====================
#define TFT_WIDTH   240
#define TFT_HEIGHT240

// ==================== 颜色定义 ====================
#define BLACK       0x0000
#define BLUE      0x001F
#define RED         0xF800
#define GREEN       0x07E0
#define CYAN      0x07FF
#define MAGENTA   0xF81F
#define YELLOW      0xFFE0
#define WHITE       0xFFFF

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

void delay_us(unsigned int us)
{
    unsigned int i;
    for(i = 0; i &lt; us; i++)
      _nop_();
}

// ==================== SPI 底层操作 ====================
// 模拟 SPI 写一个字节(MSB 先行)
void SPI_WriteByte(unsigned char dat)
{
    unsigned char i;
    for(i = 0; i &lt; 8; i++)
    {
      TFT_SCL_L;
      if(dat &amp; 0x80)
            TFT_SDA_H;
      else
            TFT_SDA_L;
      TFT_SCL_H;
      dat &lt;&lt;= 1;
    }
}

// ==================== ST7789 命令/数据写入 ====================
// 写命令
void ST7789_WriteCmd(unsigned char cmd)
{
    TFT_CS_L;
    TFT_DC_L;         // DC=0 表示命令
    SPI_WriteByte(cmd);
    TFT_CS_H;
}

// 写数据
void ST7789_WriteData(unsigned char dat)
{
    TFT_CS_L;
    TFT_DC_H;         // DC=1 表示数据
    SPI_WriteByte(dat);
    TFT_CS_H;
}

// 写16位数据(颜色值,高字节在前)
void ST7789_WriteData16(unsigned short dat)
{
    TFT_CS_L;
    TFT_DC_H;
    SPI_WriteByte((unsigned char)(dat &gt;&gt; 8));   // 高字节
    SPI_WriteByte((unsigned char)(dat &amp; 0xFF)); // 低字节
    TFT_CS_H;
}

// ==================== ST7789 初始化 ====================
void ST7789_Init(void)
{
    // 硬件复位
    TFT_RST_L;
    delay_ms(10);
    TFT_RST_H;
    delay_ms(120);

    // 初始化序列(ST7789 常用初始化命令)
    ST7789_WriteCmd(0x01);// 软件复位
    delay_ms(120);

    ST7789_WriteCmd(0x11);// 退出睡眠模式
    delay_ms(120);

    ST7789_WriteCmd(0x36);// 内存数据访问控制
    ST7789_WriteData(0x00); // 根据屏幕方向调整:0x00正常,0xC0旋转

    ST7789_WriteCmd(0x3A);// 接口像素格式
    ST7789_WriteData(0x05); // 16位色(65K色)

    ST7789_WriteCmd(0xB2);//porch 设置
    ST7789_WriteData(0x0C);
    ST7789_WriteData(0x0C);
    ST7789_WriteData(0x00);
    ST7789_WriteData(0x33);
    ST7789_WriteData(0x33);

    ST7789_WriteCmd(0xB7);// 栅极控制
    ST7789_WriteData(0x35);

    ST7789_WriteCmd(0xBB);// VCOM 设置
    ST7789_WriteData(0x19);

    ST7789_WriteCmd(0xC0);// LCM 控制
    ST7789_WriteData(0x2C);

    ST7789_WriteCmd(0xC2);// VDV 和 VRH 使能
    ST7789_WriteData(0x01);

    ST7789_WriteCmd(0xC3);// VRH 设置
    ST7789_WriteData(0x12);

    ST7789_WriteCmd(0xC4);// VDV 设置
    ST7789_WriteData(0x20);

    ST7789_WriteCmd(0xC6);// 帧率控制
    ST7789_WriteData(0x0F);

    ST7789_WriteCmd(0xD0);// 电源控制
    ST7789_WriteData(0xA4);
    ST7789_WriteData(0xA1);

    ST7789_WriteCmd(0xE0);// 正电压伽马校正
    ST7789_WriteData(0xD0);
    ST7789_WriteData(0x04);
    ST7789_WriteData(0x0D);
    ST7789_WriteData(0x11);
    ST7789_WriteData(0x13);
    ST7789_WriteData(0x2B);
    ST7789_WriteData(0x3F);
    ST7789_WriteData(0x54);
    ST7789_WriteData(0x4C);
    ST7789_WriteData(0x18);
    ST7789_WriteData(0x0D);
    ST7789_WriteData(0x0B);
    ST7789_WriteData(0x1F);
    ST7789_WriteData(0x23);

    ST7789_WriteCmd(0xE1);// 负电压伽马校正
    ST7789_WriteData(0xD0);
    ST7789_WriteData(0x04);
    ST7789_WriteData(0x0C);
    ST7789_WriteData(0x11);
    ST7789_WriteData(0x13);
    ST7789_WriteData(0x2C);
    ST7789_WriteData(0x3F);
    ST7789_WriteData(0x44);
    ST7789_WriteData(0x51);
    ST7789_WriteData(0x2F);
    ST7789_WriteData(0x1F);
    ST7789_WriteData(0x1F);
    ST7789_WriteData(0x20);
    ST7789_WriteData(0x23);

    ST7789_WriteCmd(0x21);// 反色关闭(部分屏幕需要)
    delay_ms(10);

    ST7789_WriteCmd(0x29);// 开启显示
    delay_ms(10);

    // 开启背光
    TFT_BL_H;
}

// ==================== 设置显示区域 ====================
void ST7789_SetWindow(unsigned short x0, unsigned short y0,
                      unsigned short x1, unsigned short y1)
{
    ST7789_WriteCmd(0x2A);// 列地址设置
    ST7789_WriteData16(x0);
    ST7789_WriteData16(x1);

    ST7789_WriteCmd(0x2B);// 行地址设置
    ST7789_WriteData16(y0);
    ST7789_WriteData16(y1);

    ST7789_WriteCmd(0x2C);// 写内存开始
}

// ==================== 画点函数 ====================
void ST7789_DrawPixel(unsigned short x, unsigned short y, unsigned short color)
{
    if(x &gt;= TFT_WIDTH || y &gt;= TFT_HEIGHT) return;
    ST7789_SetWindow(x, y, x, y);
    ST7789_WriteData16(color);
}

// ==================== 全屏填充 ====================
void ST7789_FillScreen(unsigned short color)
{
    unsigned long i;
    unsigned long total = (unsigned long)TFT_WIDTH * TFT_HEIGHT;

    ST7789_SetWindow(0, 0, TFT_WIDTH - 1, TFT_HEIGHT - 1);

    for(i = 0; i &lt; total; i++)
    {
      ST7789_WriteData16(color);
    }
}

// ==================== 显示矩形填充 ====================
void ST7789_FillRect(unsigned short x0, unsigned short y0,
                     unsigned short x1, unsigned short y1,
                     unsigned short color)
{
    unsigned long i, total;
    total = (unsigned long)(x1 - x0 + 1) * (y1 - y0 + 1);

    ST7789_SetWindow(x0, y0, x1, y1);

    for(i = 0; i &lt; total; i++)
    {
      ST7789_WriteData16(color);
    }
}

// ==================== 显示字符(5×7 点阵) ====================
// 标准 ASCII 5×7 字库(部分,仅含可打印字符 0x20~0x7E)
// 此处仅给出函数框架,完整字库可自行添加
extern unsigned char code ascii_5x7[];// 外部字库数组

void ST7789_ShowChar(unsigned short x, unsigned short y,
                     unsigned char ch, unsigned short fc, unsigned short bc)
{
    unsigned char i, j;
    unsigned char temp;

    if(ch &lt; 0x20 || ch &gt; 0x7E) ch = 0x20;// 不可打印字符显示空格
    ch -= 0x20;

    for(i = 0; i &lt; 5; i++)
    {
      temp = ascii_5x7;
      for(j = 0; j &lt; 7; j++)
      {
            if(temp &amp; (1 &lt;&lt; (6 - j)))
                ST7789_DrawPixel(x + i, y + j, fc);
            else
                ST7789_DrawPixel(x + i, y + j, bc);
      }
    }
}

// ==================== 显示字符串 ====================
void ST7789_ShowString(unsigned short x, unsigned short y,
                     unsigned char *str,
                     unsigned short fc, unsigned short bc)
{
    while(*str)
    {
      ST7789_ShowChar(x, y, *str, fc, bc);
      x += 6;             // 字符宽度 5 + 间距 1
      if(x &gt; TFT_WIDTH - 6)
      {
            x = 0;
            y += 8;         // 换行
      }
      str++;
    }
}

// ==================== 主函数 ====================
void main(void)
{
    // 设置系统时钟为内部高速时钟 22.1184MHz
    WTST = 0x00;    // 设置程序等待周期为 0
    EAXFR = 1;      // 允许访问 XFR

    // 设置 P1 为准双向口(默认即可)
    P1M0 = 0x00;
    P1M1 = 0x00;

    delay_ms(100);

    // 初始化 ST7789
    ST7789_Init();

    // 全屏填充测试
    ST7789_FillScreen(RED);
    delay_ms(500);
    ST7789_FillScreen(GREEN);
    delay_ms(500);
    ST7789_FillScreen(BLUE);
    delay_ms(500);
    ST7789_FillScreen(BLACK);
    delay_ms(500);

    // 显示字符串
    ST7789_ShowString(20, 20, &quot;Hello STC32G!&quot;, WHITE, BLACK);
    ST7789_ShowString(20, 30, &quot;ST7789 240x240&quot;, YELLOW, BLACK);
    ST7789_ShowString(20, 40, &quot;SPI TFT Test&quot;, CYAN, BLACK);

    // 画矩形框
    ST7789_FillRect(10, 60, 230, 230, BLUE);
    ST7789_FillRect(20, 70, 220, 220, BLACK);

    while(1)
    {
      // 主循环
    }
}
</code></pre>
<hr />
<h3>使用说明</h3>
<ol>
<li><strong>硬件连接</strong>:按上表将 ST7789 模块与 STC32G12K128 对应引脚连接。</li>
<li><strong>编译环境</strong>:使用 Keil C251 编译器,选择 STC32G12K128 型号。</li>
<li><strong>时钟设置</strong>:程序默认使用内部 22.1184MHz IRC 时钟,如需更高频率可修改。</li>
<li><strong>字库补充</strong>:程序中 <code>ascii_5x7</code> 字库数组未完整列出,您可以从网上获取标准 5×7 ASCII 字库数据添加到代码中,或使用取模软件生成。</li>
</ol>
<h3>信息来源</h3>
<ul>
<li><strong>STC32G 手册</strong>:章节 <strong>&quot;29 TFT彩屏接口接口(8/16位I8080/M6800接口)&quot;</strong> — 说明 STC32G12K128 支持 LCM 接口,但 ST7789 通常使用 SPI 而非并口。</li>
<li><strong>论坛帖子</strong>:TID=9254,作者 浦江*** — &quot;SPI-TFT(240x240)彩屏@屠龙刀-32G12K128 驱动 | TFT取代OLED12864,ST7789&quot;,采用模拟 IO 驱动 SPI 方式。</li>
</ul>
<blockquote>
<p><strong>提示</strong>:如需更高刷新率,可改用 <strong>硬件 SPI</strong>(使用 STC32G 的 SPI 模块)或 <strong>DMA-SPI</strong> 方式,论坛帖子中也提到这是后续可优化的方向。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=%E5%B8%AE%E5%86%99%20STC32G12K128%20%E9%A9%B1%E5%8A%A8%20ST7789%20%E5%B1%8F%E7%9A%84%E7%A8%8B%E5%BA%8F&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;帮写 STC32G12K128 驱动 ST7789 屏的程序&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%86%99STC32G12K128%20%E9%A9%B1%E5%8A%A8%20ST7789%20%E5%B1%8F%E7%9A%84%E7%A8%8B%E5%BA%8F&amp;page=11&amp;extra=#pid219367">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=3288&amp;highlight=%E5%B8%AE%E5%86%99STC32G12K128%20%E9%A9%B1%E5%8A%A8%20ST7789%20%E5%B1%8F%E7%9A%84%E7%A8%8B%E5%BA%8F&amp;page=1&amp;extra=#pid21975">关于使用 STC32G12K128的LCM-I8080模式驱动8位并口的st7789v的LCD遇到的问题</a></li>
<li>屠龙刀三 STC32G12K128实验板驱动TFT(128x160)彩屏, ST7735</li>
</ol>
<p>⏱️ 查找用时 20.0s | 总回答用时 50.2s</p>

芯势力 发表于 2026-7-3 14:00:42

帮 STC32G12K128自动写一个完整的演示程序,主要做摩托车定位模块
要包含下面这些:
1,串口接收命令,启动ADC转换,送回ADC转换的值
2,串口接收命令,改变PWM对外的输出频率和占空比
3, 用PWM测量外部2路输入信号各自的周期和占空比

芯势力 发表于 2026-7-3 14:00:51

<p>好的,基于已获取的完整手册信息,我现在为您编写完整的 STC32G12K128 摩托车定位模块演示程序。</p>
<hr />
<h2>STC32G12K128 摩托车定位模块演示程序</h2>
<p>以下是一个完整的 C 语言演示程序,包含您要求的三大功能。程序使用 <strong>串口1(P3.0/P3.1,即 USB 串口)</strong> 作为命令交互接口,使用 <strong>PWMA(第一组高级PWM)</strong> 实现 PWM 输出和外部信号测量。</p>
<h3>硬件引脚分配</h3>
<table>
<thead>
<tr>
<th>功能</th>
<th>引脚</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>串口1(命令交互)</td>
<td>P3.0(RxD) / P3.1(TxD)</td>
<td>USB转串口,波特率115200</td>
</tr>
<tr>
<td>ADC输入(测量电池/传感器电压)</td>
<td>P1.0(ADC0)</td>
<td>12位ADC,通道0</td>
</tr>
<tr>
<td>PWM输出(对外控制,如喇叭/灯)</td>
<td>P1.0(PWM1P)</td>
<td>PWMA通道1,可调频率和占空比</td>
</tr>
<tr>
<td>外部信号输入1(测周期/占空比)</td>
<td>P1.0(PWM1P)</td>
<td>PWMA的CC1+CC2捕获</td>
</tr>
<tr>
<td>外部信号输入2(测周期/占空比)</td>
<td>P1.4(PWM3P)</td>
<td>PWMA的CC3+CC4捕获</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注意</strong>:PWM输出和输入捕获共用同一引脚时需分时复用。实际应用中建议将 PWM 输出和输入捕获分配到不同引脚。本演示为展示全部功能,将 PWM 输出放在 PWMA 通道1(P1.0),外部信号测量使用 PWMA 通道3(P1.4)和 PWMB 通道5(P2.0 等,可自行调整)。</p>
</blockquote>
<h3>串口命令协议</h3>
<table>
<thead>
<tr>
<th>命令</th>
<th>格式</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>ADC</td>
<td><code>ADC\r\n</code></td>
<td>启动一次ADC转换,返回ADC值(十进制)</td>
</tr>
<tr>
<td>PWM</td>
<td><code>PWM,freq,duty\r\n</code></td>
<td>设置PWM输出频率(Hz)和占空比(0~1000=0.0%~100.0%)</td>
</tr>
<tr>
<td>CAP1</td>
<td><code>CAP1\r\n</code></td>
<td>读取外部信号1的周期(us)和占空比(0~1000)</td>
</tr>
<tr>
<td>CAP2</td>
<td><code>CAP2\r\n</code></td>
<td>读取外部信号2的周期(us)和占空比(0~1000)</td>
</tr>
</tbody>
</table>
<hr />
<h3>完整源代码</h3>
<pre><code class="language-c">/*------------------------------------------------------------------*/
/* --- STC32G12K128 摩托车定位模块演示程序                        */
/* 功能:                                                         */
/*   1. 串口接收命令,启动ADC转换,送回ADC转换的值                  */
/*   2. 串口接收命令,改变PWM对外的输出频率和占空比               */
/*   3. 用PWM测量外部2路输入信号各自的周期和占空比                  */
/*                                                                  */
/* 串口:UART1 (P3.0/P3.1), 115200, 8N1                            */
/* 系统时钟:内部IRC 11.0592MHz                                     */
/*------------------------------------------------------------------*/

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

#define FOSC      11059200UL      // 系统时钟频率 11.0592MHz
#define BRT         (65536 - (FOSC / 115200 + 2) / 4)// 115200波特率重装值

// 命令缓冲区
#define CMD_BUF_SIZE    16
volatile u8cmd_buf;
volatile u8cmd_len = 0;
volatile bit cmd_ready = 0;

// ADC结果
volatile u16 adc_result = 0;
volatile bit adc_done = 0;

// PWM输出参数
volatile u16 pwm_freq = 1000;       // 默认1kHz
volatile u16 pwm_duty = 500;      // 默认50.0% (0~1000)

// 外部信号捕获结果
volatile u16 cap1_period = 0;       // 信号1周期 (PWM时钟计数)
volatile u16 cap1_duty   = 0;       // 信号1高电平宽度
volatile bit cap1_ready= 0;

volatile u16 cap2_period = 0;       // 信号2周期
volatile u16 cap2_duty   = 0;       // 信号2高电平宽度
volatile bit cap2_ready= 0;

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

// 函数声明
void delay_ms(u16 ms);
void UART1_Init(void);
void UART1_SendByte(u8 dat);
void UART1_SendString(u8 *str);
void UART1_SendHex(u16 val);
void UART1_SendDec(u16 val);
void ADC_Init(void);
u16ADC_Read(u8 channel);
void PWM_Output_Init(void);
void PWM_Output_Set(u16 freq_hz, u16 duty_permille);
void PWM_Capture1_Init(void);
void PWM_Capture2_Init(void);

/*------------------------------------------------------------------*/
/* 主函数                                                            */
/*------------------------------------------------------------------*/
void main(void)
{
    u8 i;
    u32 temp;
    u16 freq_val;
    u16 duty_val;
    u16 period_us, duty_permille;

    // 系统初始化
    EAXFR = 1;            // 使能访问XFR
    CKCON = 0x00;         // 外部数据总线最快
    WTST= 0x00;         // 程序代码等待最快

    // 所有I/O口设为准双向
    P0M0 = 0x00; P0M1 = 0x00;
    P1M0 = 0x00; P1M1 = 0x00;
    P2M0 = 0x00; P2M1 = 0x00;
    P3M0 = 0x00; P3M1 = 0x00;
    P4M0 = 0x00; P4M1 = 0x00;
    P5M0 = 0x00; P5M1 = 0x00;

    // 初始化各模块
    UART1_Init();
    ADC_Init();
    PWM_Output_Init();      // 启动默认PWM输出
    PWM_Capture1_Init();    // 启动外部信号1捕获
    PWM_Capture2_Init();    // 启动外部信号2捕获

    EA = 1;               // 开总中断

    // 发送启动信息
    UART1_SendString(&quot;STC32G12K128 摩托车定位模块演示程序\r\n&quot;);
    UART1_SendString(&quot;命令列表:\r\n&quot;);
    UART1_SendString(&quot;ADC       - 读取ADC值\r\n&quot;);
    UART1_SendString(&quot;PWM,f,d   - 设置PWM频率(Hz)和占空比(0~1000)\r\n&quot;);
    UART1_SendString(&quot;CAP1      - 读取外部信号1周期和占空比\r\n&quot;);
    UART1_SendString(&quot;CAP2      - 读取外部信号2周期和占空比\r\n&quot;);
    UART1_SendString(&quot;就绪&gt;\r\n&quot;);

    while(1)
    {
      // 检查是否有完整命令
      if(cmd_ready)
      {
            cmd_ready = 0;

            // ----- 命令1: ADC -----
            if( cmd_buf=='A' &amp;&amp; cmd_buf=='D' &amp;&amp; cmd_buf=='C' )
            {
                u16 adc_val = ADC_Read(0);// 读取ADC通道0 (P1.0)
                UART1_SendString(&quot;ADC=&quot;);
                UART1_SendDec(adc_val);
                UART1_SendString(&quot;\r\n就绪&gt;\r\n&quot;);
            }
            // ----- 命令2: PWM,freq,duty -----
            else if( cmd_buf=='P' &amp;&amp; cmd_buf=='W' &amp;&amp; cmd_buf=='M' )
            {
                // 解析 &quot;PWM,1000,500&quot; 格式
                // 跳过 &quot;PWM,&quot;
                i = 4;
                freq_val = 0;
                while(cmd_buf &gt;= '0' &amp;&amp; cmd_buf &lt;= '9')
                {
                  freq_val = freq_val * 10 + (cmd_buf - '0');
                  i++;
                }
                if(cmd_buf == ',') i++;
                duty_val = 0;
                while(cmd_buf &gt;= '0' &amp;&amp; cmd_buf &lt;= '9')
                {
                  duty_val = duty_val * 10 + (cmd_buf - '0');
                  i++;
                }

                if(freq_val &gt;= 10 &amp;&amp; freq_val &lt;= 100000 &amp;&amp; duty_val &lt;= 1000)
                {
                  PWM_Output_Set(freq_val, duty_val);
                  UART1_SendString(&quot;PWM OK: &quot;);
                  UART1_SendDec(freq_val);
                  UART1_SendString(&quot;Hz, &quot;);
                  UART1_SendDec(duty_val);
                  UART1_SendString(&quot;/1000\r\n就绪&gt;\r\n&quot;);
                }
                else
                {
                  UART1_SendString(&quot;参数错误! 频率10~100000Hz, 占空比0~1000\r\n就绪&gt;\r\n&quot;);
                }
            }
            // ----- 命令3: CAP1 -----
            else if( cmd_buf=='C' &amp;&amp; cmd_buf=='A' &amp;&amp; cmd_buf=='P' &amp;&amp; cmd_buf=='1' )
            {
                if(cap1_ready)
                {
                  cap1_ready = 0;
                  // 周期 = cap1_period / (FOSC / PWMA预分频)
                  // 预分频为 FOSC/1000000 = 11 (实际约1MHz计数时钟)
                  // 所以周期(us) ≈ cap1_period
                  period_us = cap1_period;
                  duty_permille = (u16)((u32)cap1_duty * 1000 / cap1_period);

                  UART1_SendString(&quot;CAP1: Period=&quot;);
                  UART1_SendDec(period_us);
                  UART1_SendString(&quot;us, Duty=&quot;);
                  UART1_SendDec(duty_permille);
                  UART1_SendString(&quot;/1000\r\n就绪&gt;\r\n&quot;);
                }
                else
                {
                  UART1_SendString(&quot;CAP1: 无新数据\r\n就绪&gt;\r\n&quot;);
                }
            }
            // ----- 命令4: CAP2 -----
            else if( cmd_buf=='C' &amp;&amp; cmd_buf=='A' &amp;&amp; cmd_buf=='P' &amp;&amp; cmd_buf=='2' )
            {
                if(cap2_ready)
                {
                  cap2_ready = 0;
                  period_us = cap2_period;
                  duty_permille = (u16)((u32)cap2_duty * 1000 / cap2_period);

                  UART1_SendString(&quot;CAP2: Period=&quot;);
                  UART1_SendDec(period_us);
                  UART1_SendString(&quot;us, Duty=&quot;);
                  UART1_SendDec(duty_permille);
                  UART1_SendString(&quot;/1000\r\n就绪&gt;\r\n&quot;);
                }
                else
                {
                  UART1_SendString(&quot;CAP2: 无新数据\r\n就绪&gt;\r\n&quot;);
                }
            }
            else
            {
                UART1_SendString(&quot;未知命令\r\n就绪&gt;\r\n&quot;);
            }

            // 清命令缓冲区
            cmd_len = 0;
            for(i=0; i&lt;CMD_BUF_SIZE; i++) cmd_buf = 0;
      }
    }
}

/*------------------------------------------------------------------*/
/* 串口1初始化 (115200, 8N1, 定时器1做波特率发生器)                  */
/*------------------------------------------------------------------*/
void UART1_Init(void)
{
    SCON = 0x50;            // 模式1, 8位UART, 使能接收
    TMOD = 0x00;            // 定时器1模式0 (16位自动重载)
    T1x12 = 1;            // 定时器1 1T模式
    TL1 = (u8)(BRT);
    TH1 = (u8)(BRT &gt;&gt; 8);
    TR1 = 1;                // 启动定时器1
    ES= 1;                // 使能串口1中断
}

/*------------------------------------------------------------------*/
/* 串口1发送单字节 (查询方式)                                        */
/*------------------------------------------------------------------*/
void UART1_SendByte(u8 dat)
{
    TI = 0;
    SBUF = dat;
    while(!TI);
    TI = 0;
}

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

/*------------------------------------------------------------------*/
/* 串口1发送十进制数 (0~65535)                                       */
/*------------------------------------------------------------------*/
void UART1_SendDec(u16 val)
{
    u8 buf;
    u8 i = 0;
    if(val == 0)
    {
      UART1_SendByte('0');
      return;
    }
    while(val &gt; 0)
    {
      buf = (u8)(val % 10) + '0';
      val /= 10;
    }
    while(i &gt; 0)
    {
      UART1_SendByte(buf[--i]);
    }
}

/*------------------------------------------------------------------*/
/* 串口1中断服务程序 (接收命令)                                    */
/*------------------------------------------------------------------*/
void UART1_ISR(void) interrupt 4
{
    if(RI)
    {
      RI = 0;
      u8 dat = SBUF;

      // 收到回车符表示命令结束
      if(dat == '\r' || dat == '\n')
      {
            if(cmd_len &gt; 0)
            {
                cmd_buf = '\0';
                cmd_ready = 1;
            }
      }
      else if(cmd_len &lt; CMD_BUF_SIZE - 1)
      {
            // 转大写
            if(dat &gt;= 'a' &amp;&amp; dat &lt;= 'z')
                dat -= 0x20;
            cmd_buf = dat;
      }
    }
    if(TI)
    {
      TI = 0;
    }
}

/*------------------------------------------------------------------*/
/* ADC初始化                                                         */
/*------------------------------------------------------------------*/
void ADC_Init(void)
{
    P1M0 = 0x00;            // P1.0设为高阻输入 (ADC通道)
    P1M1 = 0x01;

    ADCTIM = 0x3f;          // 设置ADC内部时序
    ADCCFG = 0x2f;          // 右对齐, ADC时钟 = SYSclk/2/16
    ADC_POWER = 1;          // 使能ADC模块
    delay_ms(1);            // 等待ADC电源稳定
}

/*------------------------------------------------------------------*/
/* ADC读取 (查询方式)                                                */
/* 参数: channel = 0~15                                             */
/* 返回: 12位ADC值 (0~4095)                                       */
/*------------------------------------------------------------------*/
u16 ADC_Read(u8 channel)
{
    u16 res;

    ADC_CONTR = (ADC_CONTR &amp; 0xf0) | channel;// 选择通道
    ADC_START = 1;          // 启动转换
    _nop_(); _nop_();
    while(!ADC_FLAG);       // 等待转换完成
    ADC_FLAG = 0;         // 清标志

    // 右对齐格式: ADC_RES存高4位, ADC_RESL存低8位
    res = ((u16)(ADC_RES &amp; 0x0f) &lt;&lt; 8) | ADC_RESL;
    return res;
}

/*------------------------------------------------------------------*/
/* PWM输出初始化 (PWMA通道1, P1.0/PWM1P)                            */
/* 默认: 1kHz, 50% 占空比                                           */
/*------------------------------------------------------------------*/
void PWM_Output_Init(void)
{
    // 设置P1.0为推挽输出 (PWM输出)
    P1M0 = 0x01;
    P1M1 = 0x00;

    // PWMA时钟 = 系统时钟 / 预分频
    // 预分频 = FOSC / 1000000 - 1 ≈ 10 (使计数时钟约1MHz)
    PWMA_PSCRH = (u8)((FOSC / 1000000 - 1) &gt;&gt; 8);
    PWMA_PSCRL = (u8)(FOSC / 1000000 - 1);

    // 设置默认频率和占空比
    PWM_Output_Set(pwm_freq, pwm_duty);

    // 使能PWM1P输出
    PWMA_ENO = 0x01;      // ENO1P = 1
    PWMA_IOAUX = 0x00;      // 主输出到端口

    // 配置CC1为输出模式 (PWM模式1: 向上计数, 输出电平取反)
    PWMA_CCMR1 = 0x60;      // OC1M=110 (PWM模式1), OC1PE=1(预装载使能)
    PWMA_CCER1 = 0x00;      // CC1P=0 (高电平有效), CC1E=1
    PWMA_CCER1 |= 0x01;   // 使能CC1输出

    PWMA_CR1= 0x01;       // CENA=1, 使能计数器
    PWMA_BKR= 0x80;       // MOEA=1, 使能主输出
}

/*------------------------------------------------------------------*/
/* 设置PWM输出频率和占空比                                           */
/* freq_hz: 频率 (10~100000Hz)                                    */
/* duty_permille: 占空比 (0~1000, 对应0.0%~100.0%)                  */
/*------------------------------------------------------------------*/
void PWM_Output_Set(u16 freq_hz, u16 duty_permille)
{
    u16 arr, ccr;

    // 计数时钟 ≈ 1MHz
    // ARR = 计数时钟 / 频率 - 1
    arr = (u16)(1000000UL / freq_hz) - 1;
    if(arr &lt; 1) arr = 1;

    // CCR = ARR * 占空比 / 1000
    ccr = (u16)((u32)arr * duty_permille / 1000);

    PWMA_ARRH= (u8)(arr &gt;&gt; 8);
    PWMA_ARRL= (u8)(arr);
    PWMA_CCR1H = (u8)(ccr &gt;&gt; 8);
    PWMA_CCR1L = (u8)(ccr);

    pwm_freq= freq_hz;
    pwm_duty= duty_permille;
}

/*------------------------------------------------------------------*/
/* PWM捕获初始化 - 外部信号1 (PWMA通道3, P1.4/PWM3P)               */
/* 使用CC3捕获上升沿, CC4捕获下降沿, 同时测周期和占空比             */
/*------------------------------------------------------------------*/
void PWM_Capture1_Init(void)
{
    // P1.4设为高阻输入
    P1M0 &amp;= ~0x10;
    P1M1 |= 0x10;

    // 选择PWM3P功能到P1.4 (默认就是P1.4)
    // PWMA_PS 的 C3PS 默认=00, 即PWM3P在P1.4

    // 配置CC3捕获上升沿, CC4捕获下降沿
    PWMA_CCER2 = 0x00;          // 先关闭CC3/CC4

    PWMA_CCMR3 = 0x01;          // CC3为输入, 映射到TI3FP3 (PWM3P)
    PWMA_CCMR4 = 0x02;          // CC4为输入, 映射到TI3FP4

    PWMA_CCER2 |= 0x01;         // CC3E=1, 使能CC3捕获
    PWMA_CCER2 |= 0x00;         // CC3P=0, 上升沿捕获
    PWMA_CCER2 |= 0x10;         // CC4E=1, 使能CC4捕获
    PWMA_CCER2 |= 0x20;         // CC4P=1, 下降沿捕获

    // 设置TI3FP3上升沿为复位触发
    PWMA_SMCR = 0x54;         // TS=TI3FP3 (101), SMS=复位模式 (100)

    PWMA_IER |= 0x18;         // 使能CC3IE和CC4IE中断
}

/*------------------------------------------------------------------*/
/* PWM捕获初始化 - 外部信号2 (PWMB通道5, P2.0/PWM5)                */
/* 使用CC5捕获上升沿, CC6捕获下降沿                                 */
/*------------------------------------------------------------------*/
void PWM_Capture2_Init(void)
{
    // P2.0设为高阻输入
    P2M0 &amp;= ~0x01;
    P2M1 |= 0x01;

    // 选择PWM5功能到P2.0 (默认PWM5在P2.0)
    // PWMB_PS 的 C5PS 默认=00

    // 配置CC5捕获上升沿, CC6捕获下降沿
    PWMB_CCER1 = 0x00;          // 先关闭CC5/CC6

    PWMB_CCMR1 = 0x01;          // CC5为输入, 映射到TI5FP5
    PWMB_CCMR2 = 0x02;          // CC6为输入, 映射到TI5FP6

    PWMB_CCER1 |= 0x01;         // CC5E=1, 使能CC5捕获
    PWMB_CCER1 |= 0x00;         // CC5P=0, 上升沿捕获
    PWMB_CCER1 |= 0x10;         // CC6E=1, 使能CC6捕获
    PWMB_CCER1 |= 0x20;         // CC6P=1, 下降沿捕获

    // 设置TI5FP5上升沿为复位触发
    PWMB_SMCR = 0x54;         // TS=TI5FP5 (101), SMS=复位模式 (100)

    PWMB_IER |= 0x06;         // 使能CC5IE和CC6IE中断
}

/*------------------------------------------------------------------*/
/* PWMA中断服务程序 (中断号26) - 处理外部信号1捕获结果               */
/*------------------------------------------------------------------*/
void PWMA_ISR(void) interrupt 26
{
    // CC3捕获中断 (上升沿 - 周期)
    if(PWMA_SR1 &amp; 0x08)
    {
      PWMA_SR1 &amp;= ~0x08;
      cap1_period = (PWMA_CCR3H &lt;&lt; 8) | PWMA_CCR3L;
    }
    // CC4捕获中断 (下降沿 - 占空比/高电平宽度)
    if(PWMA_SR1 &amp; 0x10)
    {
      PWMA_SR1 &amp;= ~0x10;
      cap1_duty = (PWMA_CCR4H &lt;&lt; 8) | PWMA_CCR4L;
      cap1_ready = 1;         // 标记数据就绪
    }
}

/*------------------------------------------------------------------*/
/* PWMB中断服务程序 (中断号27) - 处理外部信号2捕获结果               */
/*------------------------------------------------------------------*/
void PWMB_ISR(void) interrupt 27
{
    // CC5捕获中断 (上升沿 - 周期)
    if(PWMB_SR1 &amp; 0x02)
    {
      PWMB_SR1 &amp;= ~0x02;
      cap2_period = (PWMB_CCR5H &lt;&lt; 8) | PWMB_CCR5L;
    }
    // CC6捕获中断 (下降沿 - 占空比/高电平宽度)
    if(PWMB_SR1 &amp; 0x04)
    {
      PWMB_SR1 &amp;= ~0x04;
      cap2_duty = (PWMB_CCR6H &lt;&lt; 8) | PWMB_CCR6L;
      cap2_ready = 1;         // 标记数据就绪
    }
}

/*------------------------------------------------------------------*/
/* 简单延时 (约1ms, 11.0592MHz下)                                    */
/*------------------------------------------------------------------*/
void delay_ms(u16 ms)
{
    u16 i, j;
    for(i=0; i&lt;ms; i++)
      for(j=0; j&lt;2760; j++);
}
</code></pre>
<hr />
<h3>程序说明</h3>
<h4>1. 串口接收命令 → ADC转换 → 返回结果</h4>
<ul>
<li>串口1(P3.0/P3.1)以 <strong>115200bps</strong> 工作,中断方式接收命令</li>
<li>收到 <code>ADC\r\n</code> 后,启动 <strong>ADC通道0(P1.0)</strong> 的12位转换</li>
<li>转换结果以十进制格式返回,如 <code>ADC=2048\r\n</code></li>
<li>ADC配置参考手册 <strong>&quot;ADC模数转换&quot;</strong> 章节,采用右对齐格式</li>
</ul>
<h4>2. 串口接收命令 → 改变PWM输出频率和占空比</h4>
<ul>
<li>收到 <code>PWM,1000,500\r\n</code> 格式命令后解析参数</li>
<li>使用 <strong>PWMA通道1(P1.0/PWM1P)</strong> 输出PWM波形</li>
<li>计数时钟约 <strong>1MHz</strong>,频率范围 <strong>10Hz ~ 100kHz</strong></li>
<li>占空比精度 <strong>0.1%</strong>(0~1000对应0.0%~100.0%)</li>
<li>配置参考手册 <strong>&quot;16位高级PWM定时器&quot;</strong> 章节</li>
</ul>
<h4>3. PWM测量外部2路输入信号的周期和占空比</h4>
<ul>
<li><strong>外部信号1</strong>:使用 <strong>PWMA的CC3+CC4</strong> 捕获 <strong>P1.4/PWM3P</strong>
<ul>
<li>CC3捕获上升沿(周期),CC4捕获下降沿(高电平宽度)</li>
<li>设置TI3FP3上升沿为复位触发,捕获值直接为周期和占空比</li>
</ul>
</li>
<li><strong>外部信号2</strong>:使用 <strong>PWMB的CC5+CC6</strong> 捕获 <strong>P2.0/PWM5</strong>
<ul>
<li>CC5捕获上升沿(周期),CC6捕获下降沿(高电平宽度)</li>
</ul>
</li>
<li>捕获原理参考手册 <strong>&quot;输入捕获模式同时测量脉冲周期和高电平宽度(占空比)&quot;</strong> 章节</li>
<li>返回格式:<code>CAP1: Period=1000us, Duty=500/1000</code></li>
</ul>
<hr />
<h3>使用说明</h3>
<ol>
<li><strong>编译下载</strong>:使用 Keil C251 编译器,选择 STC32G12K128 型号,系统时钟设为 <strong>11.0592MHz</strong></li>
<li><strong>串口连接</strong>:P3.0(RxD)/P3.1(TxD) 通过 USB转串口连接电脑,波特率 <strong>115200</strong></li>
<li><strong>硬件连接</strong>:
<ul>
<li>ADC输入:待测电压接 P1.0(0~VCC)</li>
<li>PWM输出:P1.0 输出PWM波形(需推挽驱动)</li>
<li>外部信号1:待测方波接 P1.4</li>
<li>外部信号2:待测方波接 P2.0</li>
</ul>
</li>
<li><strong>发送命令</strong>:在串口终端发送 <code>ADC</code>、<code>PWM,5000,300</code>、<code>CAP1</code>、<code>CAP2</code> 等命令</li>
</ol>
<blockquote>
<p><strong>注意</strong>:本程序为演示用途,PWM输出(P1.0)和ADC输入(P1.0)共用同一引脚,实际使用时建议将ADC分配到其他通道(如P1.1/ADC1),或将PWM输出分配到其他引脚(如P2.0/PWM5)。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=ST&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;ST&quot;</a></p>
<p>⏱️ 查找用时 22.6s | 总回答用时 72.2s</p>

AI芯启强 发表于 2026-7-3 14:10:33

帮 STC8G1K08 写一个完整的演示程序,主要功能如下:
1,串口接收命令,启动 ADC 转换,送回 ADC 转换的值
2,串口接收命令,改变 PWM 对外的输出频率和占空比

AI芯启强 发表于 2026-7-3 14:10:42

<p>好的,基于已获取的手册信息,我来编写完整的演示程序。STC8G1K08 系列有 2 个串口(8Pin 版本有 1 个),增强型 PWM 有 6 组(PWM0~PWM5),ADC 为 10 位。我以最常用的 16Pin/SOP16 封装为例,使用串口1(P3.0 RxD, P3.1 TxD),ADC 通道 0(P1.0),PWM 使用 PWM0 组的 PWM00(P1.1 或根据封装不同映射)。</p>
<p>以下是完整的 C 语言演示程序:</p>
<pre><code class="language-c">/******************************************************************************
* 文件名: main.c
* 芯片: STC8G1K08 (SOP16/DFN16等封装)
* 功能: 串口命令控制 ADC 采集与 PWM 输出
*
* 命令协议 (波特率 115200, 8N1):
*   'A' + 通道号(0~7)-&gt; 启动 ADC 转换,返回 10 位结果(高字节+低字节)
*   'F' + 频率高字节 + 频率低字节 -&gt; 设置 PWM 周期 (15位)
*   'D' + 占空比高字节 + 占空比低字节 -&gt; 设置 PWM 占空比 (T1/T2)
*   
* 硬件连接:
*   P3.0 - RxD (串口接收)
*   P3.1 - TxD (串口发送)
*   P1.0 - ADC0 (模拟输入)
*   P1.1 - PWM00 输出 (PWM 输出)
*   
* 注意: 使用内部 IRC 时钟 11.0592MHz
******************************************************************************/

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

#define MAIN_Fosc    11059200L    // 定义主时钟频率

// 串口接收缓冲区
#define RX_BUF_SIZE8
unsigned char rx_buf;
unsigned char rx_index = 0;
bit rx_complete = 0;

// 函数声明
void UART1_Init(void);
void ADC_Init(void);
void PWM_Init(void);
void PWM_SetFreq(unsigned int period);
void PWM_SetDuty(unsigned int duty);
unsigned int ADC_GetResult(unsigned char channel);
void UART1_SendByte(unsigned char dat);
void UART1_SendString(unsigned char *p);
void UART1_SendWord(unsigned int dat);

/**********************************************
* 主函数
**********************************************/
void main(void)
{
    unsigned char cmd;
    unsigned char ch;
    unsigned int adc_val;
    unsigned char freq_h, freq_l;
    unsigned char duty_h, duty_l;
    unsigned int period, duty;

    // 关闭所有中断
    EA = 0;

    // 设置系统时钟为内部 IRC 11.0592MHz
    // STC8G 上电默认使用内部 IRC,无需额外设置

    // 初始化各模块
    UART1_Init();
    ADC_Init();
    PWM_Init();

    // 开启总中断
    EA = 1;

    // 发送启动提示
    UART1_SendString(&quot;STC8G1K08 Demo Ready!\r\n&quot;);
    UART1_SendString(&quot;Commands:\r\n&quot;);
    UART1_SendString(&quot;A+ch(0-7)- ADC convert on channel\r\n&quot;);
    UART1_SendString(&quot;F+hi+lo    - Set PWM period (15bit)\r\n&quot;);
    UART1_SendString(&quot;D+hi+lo    - Set PWM duty (T1 value)\r\n&quot;);

    while(1)
    {
      if(rx_complete)
      {
            rx_complete = 0;
         
            // 解析命令
            cmd = rx_buf;
         
            switch(cmd)
            {
                case 'A':   // ADC 转换命令: 'A' + 通道号
                case 'a':
                  if(rx_index &gt;= 2)
                  {
                        ch = rx_buf;          // 通道号 0~7
                        if(ch &gt; 7) ch = 0;       // 限制范围
                     
                        adc_val = ADC_GetResult(ch);
                     
                        // 返回结果: 高字节 + 低字节
                        UART1_SendByte((unsigned char)(adc_val &gt;&gt; 8));
                        UART1_SendByte((unsigned char)(adc_val &amp; 0xFF));
                  }
                  break;
                  
                case 'F':   // 设置 PWM 频率(周期): 'F' + 高字节 + 低字节
                case 'f':
                  if(rx_index &gt;= 3)
                  {
                        freq_h = rx_buf;
                        freq_l = rx_buf;
                        period = ((unsigned int)freq_h &lt;&lt; 8) | freq_l;
                     
                        if(period &lt; 2) period = 2;      // 最小周期
                        if(period &gt; 32767) period = 32767; // 最大周期(15位)
                     
                        PWM_SetFreq(period);
                  }
                  break;
                  
                case 'D':   // 设置 PWM 占空比(T1): 'D' + 高字节 + 低字节
                case 'd':
                  if(rx_index &gt;= 3)
                  {
                        duty_h = rx_buf;
                        duty_l = rx_buf;
                        duty = ((unsigned int)duty_h &lt;&lt; 8) | duty_l;
                     
                        PWM_SetDuty(duty);
                  }
                  break;
                  
                default:
                  UART1_SendString(&quot;Unknown cmd!\r\n&quot;);
                  break;
            }
         
            rx_index = 0;
      }
    }
}

/**********************************************
* 串口1初始化 (模式1, 波特率 115200)
* 使用定时器2作为波特率发生器
* 系统时钟 11.0592MHz
**********************************************/
void UART1_Init(void)
{
    // 设置串口1引脚: P3.0 RxD, P3.1 TxD (默认)
    // P_SW1 &amp;= 0x3F;// S1_S = 00

    // 配置串口1为模式1 (8位UART, 可变波特率)
    SCON = 0x50;    // SM0=0, SM1=1, REN=1, 模式1, 允许接收

    // 使用定时器2作为波特率发生器
    // 波特率 = SYSclk / 4 / (65536 - RL2)
    // 115200 = 11059200 / 4 / (65536 - RL2)
    // RL2 = 65536 - 11059200 / 4 / 115200 = 65536 - 24 = 65512 = 0xFFE8
    AUXR &amp;= ~0x01;// S1ST2 = 0, 先选择定时器1
    T2L = 0xE8;   // 定时器2低字节
    T2H = 0xFF;   // 定时器2高字节
    AUXR |= 0x10;   // T2R = 1, 启动定时器2
    AUXR |= 0x01;   // S1ST2 = 1, 选择定时器2作为波特率发生器

    // 使能串口1中断
    ES = 1;
}

/**********************************************
* ADC 初始化
**********************************************/
void ADC_Init(void)
{
    // 设置 ADC 时钟
    // FADC = SYSclk / 2 / (SPEED+1)
    // 选择 SPEED=15, FADC = 11059200/2/16 = 345.6KHz
    ADCCFG = 0x0F;   // RESFMT=0 (左对齐), SPEED=15

    // 设置 ADC 时序
    // CSSETUP=0(1个时钟), CSHOLD=01(2个时钟), SMPDUTY=01010(11个时钟)
    // 使用默认值即可

    // 开启 ADC 电源
    ADC_CONTR = 0x80;// ADC_POWER=1
    delay_ms(1);       // 等待 ADC 电源稳定
}

/**********************************************
* PWM 初始化 (使用 PWM0 组, PWM00 通道)
* 默认: 周期 1000, 占空比 50%
**********************************************/
void PWM_Init(void)
{
    // 注意: 访问 PWM 的 XFR 寄存器需要先设置 EAXFR
    P_SW2 |= 0x80;   // EAXFR = 1, 使能访问 XFR

    // 设置 PWM0 时钟: 使用系统时钟, 不分频
    // PWM0CKS: SELT2=0, PWM_PS=0 =&gt; SYSclk/1
    PWM0CKS = 0x00;

    // 设置 PWM0 周期为 1000 (15位)
    PWM0CH = (unsigned char)((1000 - 1) &gt;&gt; 8);   // 周期高7位
    PWM0CL = (unsigned char)((1000 - 1) &amp; 0xFF); // 周期低8位

    // 设置 PWM00 的 T1 和 T2
    // T1: 计数值到达时输出低电平 (占空比控制点)
    // T2: 计数值到达时输出高电平 (周期复位点)
    // 占空比 = (周期 - T1) / 周期 * 100%
    // 50% 占空比: T1 = 500
    PWM00T1H = (unsigned char)(500 &gt;&gt; 8);
    PWM00T1L = (unsigned char)(500 &amp; 0xFF);
    PWM00T2H = 0x00;   // T2 = 0 (计数值到达0时输出高电平)
    PWM00T2L = 0x00;

    // 配置 PWM00 通道控制寄存器
    // ENO=1 (使能PWM输出), INI=0 (初始低电平)
    PWM00CR = 0x80;    // ENO=1, 其他位默认

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

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

    // 关闭 EAXFR (可选)
    // P_SW2 &amp;= 0x7F;
}

/**********************************************
* 设置 PWM 频率 (周期)
* period: 1~32767 (15位)
**********************************************/
void PWM_SetFreq(unsigned int period)
{
    P_SW2 |= 0x80;   // EAXFR = 1

    // 设置新周期
    PWM0CH = (unsigned char)((period - 1) &gt;&gt; 8);
    PWM0CL = (unsigned char)((period - 1) &amp; 0xFF);

    // 如果当前占空比大于新周期,自动调整
    // 读取当前 T1 值
    unsigned int cur_t1;
    cur_t1 = ((unsigned int)PWM00T1H &lt;&lt; 8) | PWM00T1L;
    if(cur_t1 &gt;= period)
    {
      PWM00T1H = (unsigned char)((period &gt;&gt; 1) &gt;&gt; 8);   // 设为50%
      PWM00T1L = (unsigned char)((period &gt;&gt; 1) &amp; 0xFF);
    }

    P_SW2 &amp;= 0x7F;
}

/**********************************************
* 设置 PWM 占空比
* duty: T1 值 (0~周期值)
* 占空比 = (周期 - duty) / 周期 * 100%
**********************************************/
void PWM_SetDuty(unsigned int duty)
{
    unsigned int period;

    P_SW2 |= 0x80;   // EAXFR = 1

    // 读取当前周期
    period = ((unsigned int)PWM0CH &lt;&lt; 8) | PWM0CL;
    period += 1;      // 实际周期 = 寄存器值 + 1

    // 限制占空比范围
    if(duty &gt;= period) duty = period - 1;

    // 设置 T1 (占空比控制点)
    PWM00T1H = (unsigned char)(duty &gt;&gt; 8);
    PWM00T1L = (unsigned char)(duty &amp; 0xFF);

    P_SW2 &amp;= 0x7F;
}

/**********************************************
* ADC 转换函数
* channel: ADC 通道号 (0~7 对应 P1.0~P1.7)
* 返回: 10位 ADC 结果 (左对齐, 高8位+低2位)
**********************************************/
unsigned int ADC_GetResult(unsigned char channel)
{
    unsigned int result;

    // 设置 ADC 通道对应的 I/O 口为高阻输入模式
    // P1.0~P1.7 对应通道 0~7
    switch(channel)
    {
      case 0: P1M0 &amp;= ~0x01; P1M1 |= 0x01; break;// P1.0 高阻
      case 1: P1M0 &amp;= ~0x02; P1M1 |= 0x02; break;// P1.1 高阻
      case 2: P1M0 &amp;= ~0x04; P1M1 |= 0x04; break;// P1.2 高阻
      case 3: P1M0 &amp;= ~0x08; P1M1 |= 0x08; break;// P1.3 高阻
      case 4: P1M0 &amp;= ~0x10; P1M1 |= 0x10; break;// P1.4 高阻
      case 5: P1M0 &amp;= ~0x20; P1M1 |= 0x20; break;// P1.5 高阻
      case 6: P1M0 &amp;= ~0x40; P1M1 |= 0x40; break;// P1.6 高阻
      case 7: P1M0 &amp;= ~0x80; P1M1 |= 0x80; break;// P1.7 高阻
    }

    // 选择 ADC 通道并启动转换
    ADC_CONTR = 0x80 | 0x40 | channel;// ADC_POWER=1, ADC_START=1, 选择通道

    // 等待转换完成
    while(!(ADC_CONTR &amp; 0x20));// 等待 ADC_FLAG=1

    // 读取结果 (左对齐模式: ADC_RES 高8位, ADC_RESL 低2位)
    result = ((unsigned int)ADC_RES &lt;&lt; 2) | (ADC_RESL &amp; 0x03);

    // 清除标志
    ADC_CONTR &amp;= ~0x20;// 清除 ADC_FLAG

    return result;
}

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

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

/**********************************************
* 串口发送16位数据 (2字节)
**********************************************/
void UART1_SendWord(unsigned int dat)
{
    UART1_SendByte((unsigned char)(dat &gt;&gt; 8));
    UART1_SendByte((unsigned char)(dat &amp; 0xFF));
}

/**********************************************
* 简单延时函数 (约 1ms @ 11.0592MHz)
**********************************************/
void delay_ms(unsigned int ms)
{
    unsigned int i, j;
    for(i = 0; i &lt; ms; i++)
    {
      for(j = 0; j &lt; 1350; j++);
    }
}

/**********************************************
* 串口1中断服务程序
**********************************************/
void UART1_ISR(void) interrupt 4
{
    unsigned char dat;

    if(RI)    // 接收中断
    {
      RI = 0;
      dat = SBUF;
      
      if(rx_index &lt; RX_BUF_SIZE)
      {
            rx_buf = dat;
      }
      
      // 简单判断命令结束:
      // 'A' 命令需要 2 字节
      // 'F' 和 'D' 命令需要 3 字节
      if(rx_index &gt;= 3)
      {
            rx_complete = 1;
      }
      else if(rx_index &gt;= 2 &amp;&amp; rx_buf == 'A')
      {
            rx_complete = 1;
      }
    }

    if(TI)    // 发送中断 (未使用)
    {
      TI = 0;
    }
}
</code></pre>
<hr />
<h2>程序说明</h2>
<h3>1. 命令协议(串口 115200-8N1)</h3>
<table>
<thead>
<tr>
<th align="center">命令</th>
<th align="center">格式</th>
<th align="center">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><strong>A</strong></td>
<td align="center"><code>A</code> + <code>通道号(0~7)</code></td>
<td align="center">启动指定通道 ADC 转换,返回 2 字节(10位结果,左对齐)</td>
</tr>
<tr>
<td align="center"><strong>F</strong></td>
<td align="center"><code>F</code> + <code>周期高字节</code> + <code>周期低字节</code></td>
<td align="center">设置 PWM 周期(15位,1~32767)</td>
</tr>
<tr>
<td align="center"><strong>D</strong></td>
<td align="center"><code>D</code> + <code>T1高字节</code> + <code>T1低字节</code></td>
<td align="center">设置 PWM 占空比(T1值,0~周期-1)</td>
</tr>
</tbody>
</table>
<h3>2. 硬件资源分配</h3>
<table>
<thead>
<tr>
<th align="center">引脚</th>
<th align="center">功能</th>
<th align="center">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">P3.0</td>
<td align="center">RxD</td>
<td align="center">串口1接收</td>
</tr>
<tr>
<td align="center">P3.1</td>
<td align="center">TxD</td>
<td align="center">串口1发送</td>
</tr>
<tr>
<td align="center">P1.0~P1.7</td>
<td align="center">ADC0~ADC7</td>
<td align="center">模拟输入通道(使用时自动设为高阻输入)</td>
</tr>
<tr>
<td align="center">P1.1</td>
<td align="center">PWM00</td>
<td align="center">PWM 输出(默认周期1000,占空比50%)</td>
</tr>
</tbody>
</table>
<h3>3. PWM 频率与占空比计算</h3>
<ul>
<li><strong>PWM 输出频率</strong> = SYSclk / (PWM_PS+1) / 周期​例如:11.0592MHz / 1 / 1000 = 11.0592KHz</li>
<li><strong>占空比</strong> = (周期 - T1) / 周期 × 100%<br />
例如:周期=1000,T1=500 → 占空比=50%</li>
</ul>
<h3>4. 使用示例(串口助手发送十六进制)</h3>
<pre><code>发送: 41 00   -&gt; 'A' + 通道0,启动 ADC0 转换
返回: 02 80   -&gt; ADC 结果 = 0x0280 (左对齐,实际10位值=0x0A0=160)

发送: 46 03 E7-&gt; 'F' + 0x03E7,设置周期=1000
发送: 44 01 F3-&gt; 'D' + 0x01F3,设置 T1=499,占空比≈50.1%
</code></pre>
<h3>5. 注意事项</h3>
<ol>
<li><strong>ADC 输入引脚</strong>必须设置为<strong>高阻输入模式</strong>(程序中已自动处理)。</li>
<li>PWM 的 XFR 寄存器(地址 0xFF00 以上)需要通过 <code>P_SW2 |= 0x80</code> 使能 EAXFR 位才能访问。</li>
<li>程序使用<strong>定时器2</strong>作为串口波特率发生器,系统时钟为 <strong>11.0592MHz</strong>,波特率 <strong>115200</strong>。</li>
<li>如需更改系统时钟频率,需同步调整波特率重装值(T2L/T2H)。</li>
<li>本程序基于 <strong>STC8G.h</strong> 头文件编译,请在 Keil C251 或 SDCC 环境下使用。</li>
</ol>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC8G1K08%20%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%20%E4%B8%B2%E5%8F%A3%E6%8E%A5%E6%94%B6%E5%91%BD%E4%BB%A4&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC8G1K08 演示程序 串口接收命令&quot;</a></p>
<p>⏱️ 查找用时 16.5s | 总回答用时 54.7s</p>

芯启航 发表于 2026-7-3 14:13:29

帮STC8G1K08-36I-SOP8 编写全套演示代码,需求如下:
串口接收字符指令,触发单通道内部 ADC 检测电源电压,采样结果回传上位机;
单路硬件 PWM 输出控制指示灯,串口下发 0~100 数值实时修改占空比;
上电自动读取 EEPROM 存储的默认亮度,调节亮度后一键保存至 EEPROM,断电重启保留设置;
低功耗 Stop 休眠模式,串口数据唤醒芯片,无操作定时进入休眠降低功耗。

芯启航 发表于 2026-7-3 14:13:42

<p>好的,根据已获取的手册数据,我来为您编写完整的演示代码。</p>
<p><strong>重要说明:</strong> 根据手册表格,<strong>STC8G1K08-8Pin系列</strong>(SOP8封装)的PCA功能列为空,即该系列<strong>没有PCA/PWM模块</strong>。但STC8G1K08系列(非8Pin)有PCA。您指定的型号 <code>STC8G1K08-36I-SOP8</code> 属于 <strong>STC8G1K08-8Pin系列</strong>,因此没有硬件PWM输出能力。</p>
<p>不过,我可以使用 <strong>定时器模拟PWM</strong> 的方式来实现指示灯亮度控制。以下是完整代码:</p>
<hr />
<h2>STC8G1K08-36I-SOP8 全套演示代码</h2>
<h3>引脚分配(SOP8封装)</h3>
<table>
<thead>
<tr>
<th align="center">引脚</th>
<th align="center">功能</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center">1 (P3.4)</td>
<td align="center"><strong>PWM_OUT</strong></td>
<td align="left">指示灯PWM输出(定时器模拟)</td>
</tr>
<tr>
<td align="center">2 (P3.3)</td>
<td align="center">-</td>
<td align="left">预留</td>
</tr>
<tr>
<td align="center">3 (P3.2)</td>
<td align="center">-</td>
<td align="left">预留</td>
</tr>
<tr>
<td align="center">4 (P3.1)</td>
<td align="center"><strong>TXD</strong></td>
<td align="left">串口发送</td>
</tr>
<tr>
<td align="center">5 (P3.0)</td>
<td align="center"><strong>RXD</strong></td>
<td align="left">串口接收</td>
</tr>
<tr>
<td align="center">6 (P5.4/RST)</td>
<td align="center"><strong>RST</strong></td>
<td align="left">复位(低电平复位)</td>
</tr>
<tr>
<td align="center">7 (VCC)</td>
<td align="center"><strong>电源</strong></td>
<td align="left">2.0~5.5V</td>
</tr>
<tr>
<td align="center">8 (GND)</td>
<td align="center"><strong>地</strong></td>
<td align="left">电源地</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注意:</strong> SOP8封装的引脚为:P3.0~P3.5 + VCC + GND。其中P3.0(RxD)/P3.1(TxD)用于串口,P3.4用于PWM输出,P3.5可作为复位或I/O。</p>
</blockquote>
<hr />
<h3>完整代码(main.c)</h3>
<pre><code class="language-c">/*************************************************************
* STC8G1K08-36I-SOP8 全套演示代码
*
* 功能:
*   1. 串口接收字符指令,触发ADC检测电源电压并回传
*   2. 定时器模拟PWM控制指示灯,串口下发0~100修改占空比
*   3. 上电读取EEPROM存储的默认亮度,一键保存至EEPROM
*   4. 无操作定时进入Stop休眠,串口数据唤醒
*
* 串口指令(单字符):
*   'V' / 'v'- 读取电源电压
*   '0'~'9'    - 设置亮度10%~90%(数字0=10%, 9=90%)
*   '+'      - 亮度+10
*   '-'      - 亮度-10
*   'S' / 's'- 保存当前亮度到EEPROM
*   'R' / 'r'- 读取EEPROM亮度并应用
*
* 系统时钟:内部IRC 11.0592MHz
* 串口波特率:115200bps(定时器1作为波特率发生器)
* PWM频率:约1kHz(定时器0模拟)
*
* 注意:STC8G1K08-8Pin系列无硬件PCA/PWM模块,
*       此处使用定时器0中断模拟PWM输出。
*************************************************************/

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

/*************** 用户配置宏 ***************/
#define SYS_CLK         11059200UL      // 系统时钟 11.0592MHz
#define BAUD_RATE       115200UL      // 串口波特率
#define PWM_FREQ      1000UL          // PWM频率 1kHz
#define PWM_PERIOD      (SYS_CLK / 12 / PWM_FREQ)// 定时器0 12T模式周期值
#define ADC_VREF      1.19f         // 内部参考电压 1.19V
#define EEPROM_ADDR   0x0000          // EEPROM存储地址(亮度值)
#define IDLE_TIMEOUT    30000         // 无操作超时(约30秒,主循环计数)

/*************** 全局变量 ***************/
volatile uint8_tg_pwm_duty = 50;      // 当前PWM占空比 0~100
volatile uint8_tg_pwm_counter = 0;    // PWM计数器
volatile bit      g_pwm_high = 1;       // PWM当前电平
volatile uint16_t g_idle_count = 0;   // 空闲计数
volatile bit      g_uart_rx_flag = 0;   // 串口收到新数据标志
volatile uint8_tg_uart_rx_data = 0;   // 串口接收数据
volatile bit      g_adc_done = 0;       // ADC转换完成标志
volatile uint16_t g_adc_result = 0;   // ADC结果

/*************** 函数声明 ***************/
void System_Init(void);
void UART1_Init(void);
void Timer0_Init(void);
void Timer1_Init(void);
void ADC_Init(void);
void EEPROM_Init(void);
uint8_t EEPROM_ReadByte(uint16_t addr);
void EEPROM_WriteByte(uint16_t addr, uint8_t dat);
void EEPROM_EraseSector(uint16_t addr);
void PWM_SetDuty(uint8_t duty);
void ADC_Start(void);
uint16_t ADC_GetResult(void);
float ADC_GetVoltage(void);
void UART1_SendByte(uint8_t dat);
void UART1_SendString(uint8_t *str);
void UART1_SendNum(uint16_t num);
void UART1_SendFloat(float val, uint8_t dec);
void Process_Command(uint8_t cmd);
void Enter_Stop_Mode(void);
void Delay_ms(uint16_t ms);

/*************** 主函数 ***************/
void main(void)
{
    uint8_t saved_duty;

    System_Init();          // 系统初始化
    UART1_Init();         // 串口1初始化
    Timer0_Init();          // 定时器0初始化(PWM)
    Timer1_Init();          // 定时器1初始化(串口波特率)
    ADC_Init();             // ADC初始化
    EEPROM_Init();          // EEPROM初始化

    EA = 1;               // 开启总中断

    // 上电从EEPROM读取默认亮度
    saved_duty = EEPROM_ReadByte(EEPROM_ADDR);
    if ((saved_duty &gt;= 0) &amp;&amp; (saved_duty &lt;= 100))
    {
      g_pwm_duty = saved_duty;
    }
    else
    {
      g_pwm_duty = 50;    // 默认50%
    }
    PWM_SetDuty(g_pwm_duty);

    // 发送开机提示
    UART1_SendString(&quot;STC8G1K08 Demo Ready\r\n&quot;);
    UART1_SendString(&quot;Duty: &quot;);
    UART1_SendNum(g_pwm_duty);
    UART1_SendString(&quot;%\r\n&quot;);
    UART1_SendString(&quot;Cmd: V=Volt +-=Duty S=Save R=Read\r\n&quot;);

    while (1)
    {
      // 处理串口接收到的指令
      if (g_uart_rx_flag)
      {
            g_uart_rx_flag = 0;
            g_idle_count = 0;         // 复位空闲计数器
            Process_Command(g_uart_rx_data);
      }
      
      // 空闲超时进入休眠
      if (g_idle_count &gt;= IDLE_TIMEOUT)
      {
            UART1_SendString(&quot;\r\nEnter Stop Mode...\r\n&quot;);
            Delay_ms(10);               // 等待发送完成
            Enter_Stop_Mode();
            // 唤醒后继续
            g_idle_count = 0;
            UART1_SendString(&quot;\r\nWake Up!\r\n&quot;);
      }
    }
}

/*************** 系统初始化 ***************/
void System_Init(void)
{
    // 切换内部IRC时钟为11.0592MHz
    // STC8G上电默认使用内部IRC,无需额外配置

    // 设置I/O口模式
    // P3.0(RxD) - 高阻输入(串口接收)
    // P3.1(TxD) - 推挽输出(串口发送)
    // P3.4(PWM) - 推挽输出(指示灯)
    // P3.2/P3.3/P3.5 - 准双向口
    P3M0 = 0x12;    // 0001 0010: P3.1=推挽, P3.4=推挽
    P3M1 = 0x01;    // 0000 0001: P3.0=高阻

    // 关闭不用的外设以降低功耗
    // (默认已关闭)
}

/*************** 串口1初始化 ***************/
void UART1_Init(void)
{
    // 串口1模式1:8位UART,可变波特率
    // 使用定时器1作为波特率发生器(模式2,8位自动重载)
    // 波特率 = SYSclk / 12 / (256 - TH1) / 2
    // 115200 = 11059200 / 12 / (256 - TH1) / 2
    // TH1 = 256 - 11059200 / 12 / 2 / 115200 = 256 - 4 = 252 = 0xFCH

    SCON = 0x50;            // 模式1, REN=1 允许接收
    AUXR &amp;= ~0x01;          // S1ST2=0, 选择定时器1作为波特率发生器
    AUXR |= 0x40;         // T1x12=1, 定时器1 1T模式
    TMOD &amp;= 0x0F;         // 定时器1模式0(16位自动重载)
    TMOD |= 0x20;         // 定时器1模式2(8位自动重载)

    // 计算重载值:115200bps @ 11.0592MHz, 1T模式
    // 波特率 = SYSclk / (256 - TH1) / 4
    // TH1 = 256 - SYSclk / 4 / 115200
    // TH1 = 256 - 11059200 / 4 / 115200 = 256 - 24 = 232 = 0xE8
    TH1 = 0xE8;
    TL1 = 0xE8;

    TR1 = 1;                // 启动定时器1

    ES = 1;               // 使能串口1中断
}

/*************** 定时器0初始化(PWM模拟)***************/
void Timer0_Init(void)
{
    // 定时器0模式0(16位自动重载),12T模式
    // 中断频率 = SYSclk / 12 / (65536 - 重载值)
    // 目标PWM频率 = 1kHz,每个周期100级占空比
    // 所以定时器中断频率 = 100kHz
    // 重载值 = 65536 - 11059200 / 12 / 100000 = 65536 - 9.216 ≈ 65527
    // 取整:重载值 = 65536 - 9 = 65527 = 0xFFF7

    AUXR &amp;= ~0x80;          // T0x12=0, 12T模式
    TMOD &amp;= 0xF0;         // 清除T0模式
    TMOD |= 0x00;         // T0模式0(16位自动重载)

    TH0 = 0xFF;             // 重载值高8位
    TL0 = 0xF7;             // 重载值低8位

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

/*************** 定时器1初始化(串口波特率)***************/
void Timer1_Init(void)
{
    // 已在UART1_Init中完成配置
}

/*************** ADC初始化 ***************/
void ADC_Init(void)
{
    // ADC配置:10位,右对齐,时钟 = SYSclk/2/16
    ADCCFG = 0x0F;          // RESFMT=0(右对齐), SPEED=15(SYSclk/2/16)

    // ADC时序配置
    ADCTIM = 0x3F;          // CSSETUP=0, CSHOLD=1, SMPDUTY=15

    // 选择ADC通道15(内部1.19V参考信号源)
    // 用于反推VCC电压
    ADC_CONTR = 0x8F;       // ADC_POWER=1, ADC_CHS=1111(通道15)

    Delay_ms(1);            // 等待ADC稳定
}

/*************** EEPROM初始化 ***************/
void EEPROM_Init(void)
{
    // 设置EEPROM等待时间
    // 系统时钟11.0592MHz,取整为11
    IAP_TPS = 11;         // 等待时间 = 11个系统时钟
}

/*************** EEPROM读字节 ***************/
uint8_t EEPROM_ReadByte(uint16_t addr)
{
    uint8_t dat;

    IAP_CONTR = 0x80;       // IAPEN=1, 使能EEPROM
    IAP_CMD = 0x01;         // 读命令
    IAP_ADDRH = (uint8_t)(addr &gt;&gt; 8);
    IAP_ADDRL = (uint8_t)(addr &amp; 0xFF);

    IAP_TRIG = 0x5A;      // 触发命令1
    IAP_TRIG = 0xA5;      // 触发命令2
    _nop_();                // 等待操作完成

    dat = IAP_DATA;         // 读取数据

    IAP_CONTR = 0x00;       // 关闭EEPROM

    return dat;
}

/*************** EEPROM写字节 ***************/
void EEPROM_WriteByte(uint16_t addr, uint8_t dat)
{
    IAP_CONTR = 0x80;       // IAPEN=1
    IAP_CMD = 0x02;         // 写命令
    IAP_ADDRH = (uint8_t)(addr &gt;&gt; 8);
    IAP_ADDRL = (uint8_t)(addr &amp; 0xFF);
    IAP_DATA = dat;

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

    IAP_CONTR = 0x00;
}

/*************** EEPROM擦除扇区 ***************/
void EEPROM_EraseSector(uint16_t addr)
{
    IAP_CONTR = 0x80;       // IAPEN=1
    IAP_CMD = 0x03;         // 擦除命令
    IAP_ADDRH = (uint8_t)(addr &gt;&gt; 8);
    IAP_ADDRL = (uint8_t)(addr &amp; 0xFF);

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

    IAP_CONTR = 0x00;
}

/*************** 设置PWM占空比 ***************/
void PWM_SetDuty(uint8_t duty)
{
    if (duty &gt; 100) duty = 100;
    g_pwm_duty = duty;
}

/*************** 启动ADC转换 ***************/
void ADC_Start(void)
{
    ADC_CONTR |= 0x40;      // ADC_START=1, 启动转换
}

/*************** 获取ADC结果 ***************/
uint16_t ADC_GetResult(void)
{
    uint16_t res;
    res = (uint16_t)(ADC_RES) &lt;&lt; 2;   // 10位结果,高8位在ADC_RES
    res |= (ADC_RESL &amp; 0x03);         // 低2位在ADC_RESL
    return res;
}

/*************** 获取电源电压(通过内部1.19V反推)***************/
float ADC_GetVoltage(void)
{
    uint16_t adc_bgv;
    float voltage;

    // 切换到内部1.19V通道(ADC15)
    ADC_CONTR = (ADC_CONTR &amp; 0x8F) | 0x0F;// ADC_CHS=1111
    Delay_ms(1);

    // 启动ADC转换
    g_adc_done = 0;
    ADC_Start();
    while (!g_adc_done);    // 等待转换完成

    adc_bgv = g_adc_result;

    if (adc_bgv == 0) return 0;

    // VCC = 1.19V * 1024 / adc_bgv
    // 10位ADC,满量程1024对应VCC
    voltage = (float)ADC_VREF * 1024.0f / (float)adc_bgv;

    return voltage;
}

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

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

/*************** 串口发送数字(十进制)***************/
void UART1_SendNum(uint16_t num)
{
    uint8_t buf;
    uint8_t i = 0;

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

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

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

/*************** 串口发送浮点数 ***************/
void UART1_SendFloat(float val, uint8_t dec)
{
    uint16_t int_part;
    uint16_t frac_part;
    uint16_t multiplier = 1;
    uint8_t i;

    // 处理负数
    if (val &lt; 0)
    {
      UART1_SendByte('-');
      val = -val;
    }

    // 整数部分
    int_part = (uint16_t)val;
    UART1_SendNum(int_part);

    // 小数部分
    if (dec &gt; 0)
    {
      UART1_SendByte('.');
      for (i = 0; i &lt; dec; i++) multiplier *= 10;
      frac_part = (uint16_t)((val - int_part) * multiplier + 0.5f);
      
      // 补零
      for (i = 0; i &lt; dec; i++)
      {
            if (frac_part &lt; (multiplier / 10))
            {
                UART1_SendByte('0');
            }
            multiplier /= 10;
      }
      
      if (frac_part &gt; 0)
      {
            UART1_SendNum(frac_part);
      }
    }
}

/*************** 处理串口指令 ***************/
void Process_Command(uint8_t cmd)
{
    uint8_t duty;
    float voltage;

    switch (cmd)
    {
      case 'V':
      case 'v':
            // 读取电源电压
            UART1_SendString(&quot;VCC=&quot;);
            voltage = ADC_GetVoltage();
            UART1_SendFloat(voltage, 2);
            UART1_SendString(&quot;V\r\n&quot;);
            break;
         
      case '+':
            // 亮度+10
            duty = g_pwm_duty + 10;
            if (duty &gt; 100) duty = 100;
            PWM_SetDuty(duty);
            UART1_SendString(&quot;Duty: &quot;);
            UART1_SendNum(g_pwm_duty);
            UART1_SendString(&quot;%\r\n&quot;);
            break;
         
      case '-':
            // 亮度-10
            duty = g_pwm_duty;
            if (duty &gt;= 10) duty -= 10;
            else duty = 0;
            PWM_SetDuty(duty);
            UART1_SendString(&quot;Duty: &quot;);
            UART1_SendNum(g_pwm_duty);
            UART1_SendString(&quot;%\r\n&quot;);
            break;
         
      case 'S':
      case 's':
            // 保存亮度到EEPROM
            // 先擦除扇区,再写入
            EEPROM_EraseSector(EEPROM_ADDR);
            EEPROM_WriteByte(EEPROM_ADDR, g_pwm_duty);
            UART1_SendString(&quot;Saved: &quot;);
            UART1_SendNum(g_pwm_duty);
            UART1_SendString(&quot;%\r\n&quot;);
            break;
         
      case 'R':
      case 'r':
            // 从EEPROM读取亮度
            duty = EEPROM_ReadByte(EEPROM_ADDR);
            if ((duty &gt;= 0) &amp;&amp; (duty &lt;= 100))
            {
                PWM_SetDuty(duty);
                UART1_SendString(&quot;Restored: &quot;);
                UART1_SendNum(g_pwm_duty);
                UART1_SendString(&quot;%\r\n&quot;);
            }
            else
            {
                UART1_SendString(&quot;EEPROM data invalid\r\n&quot;);
            }
            break;
         
      default:
            // 数字键 '0'~'9' -&gt; 10%~90%
            if ((cmd &gt;= '0') &amp;&amp; (cmd &lt;= '9'))
            {
                duty = (cmd - '0') * 10 + 10;   // '0'=10%, '9'=100%
                if (duty &gt; 100) duty = 100;
                PWM_SetDuty(duty);
                UART1_SendString(&quot;Duty: &quot;);
                UART1_SendNum(g_pwm_duty);
                UART1_SendString(&quot;%\r\n&quot;);
            }
            else
            {
                UART1_SendString(&quot;Unknown cmd: &quot;);
                UART1_SendByte(cmd);
                UART1_SendString(&quot;\r\n&quot;);
            }
            break;
    }
}

/*************** 进入Stop休眠模式 ***************/
void Enter_Stop_Mode(void)
{
    // 进入掉电模式前关闭不用的外设
    // 关闭定时器0(PWM)以降低功耗
    TR0 = 0;

    // 关闭ADC
    ADC_CONTR &amp;= ~0x80;   // ADC_POWER=0

    // 等待串口发送完成
    while (!TI);
    TI = 0;

    // 设置串口唤醒:串口接收数据可唤醒掉电模式
    // STC8G系列串口在掉电模式下,RxD上的下降沿可唤醒MCU
    // 需要保持REN=1

    // 进入掉电模式(Stop模式)
    PCON |= 0x02;         // PD=1, 进入掉电模式
    _nop_();
    _nop_();
    _nop_();

    // 唤醒后恢复
    // 重新初始化外设
    Timer0_Init();          // 重新启动PWM
    ADC_Init();             // 重新初始化ADC
    PWM_SetDuty(g_pwm_duty); // 恢复PWM占空比
}

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

/*************** 定时器0中断服务程序(PWM模拟)***************/
void Timer0_ISR(void) interrupt 1
{
    // 每100个中断周期完成一个PWM周期
    // 每个中断周期约10us,100个周期=1ms=1kHz

    g_pwm_counter++;
    if (g_pwm_counter &gt;= 100)
    {
      g_pwm_counter = 0;
    }

    // 根据占空比控制PWM输出
    if (g_pwm_counter &lt; g_pwm_duty)
    {
      P34 = 1;            // 高电平
    }
    else
    {
      P34 = 0;            // 低电平
    }
}

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

/*************** ADC中断服务程序 ***************/
void ADC_ISR(void) interrupt 5
{
    if (ADC_CONTR &amp; 0x20)// ADC_FLAG
    {
      ADC_CONTR &amp;= ~0x20; // 清除标志
      g_adc_result = ADC_GetResult();
      g_adc_done = 1;
    }
}
</code></pre>
<hr />
<h3>配套头文件(STC8G.h)关键寄存器定义</h3>
<pre><code class="language-c">// STC8G1K08 关键SFR定义(部分,完整请使用STC-ISP生成)
#ifndef __STC8G_H__
#define __STC8G_H__

#include

// 特殊功能寄存器
sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;
sfr P4 = 0xC0;
sfr P5 = 0xC8;

sfr P0M0 = 0x94;
sfr P0M1 = 0x93;
sfr P1M0 = 0x92;
sfr P1M1 = 0x91;
sfr P2M0 = 0x96;
sfr P2M1 = 0x95;
sfr P3M0 = 0xB2;
sfr P3M1 = 0xB1;
sfr P4M0 = 0xB4;
sfr P4M1 = 0xB3;
sfr P5M0 = 0xCA;
sfr P5M1 = 0xC9;

sfr AUXR = 0x8E;
sfr PCON = 0x87;
sfr SCON = 0x98;
sfr SBUF = 0x99;
sfr TMOD = 0x89;
sfr TCON = 0x88;
sfr TL0 = 0x8A;
sfr TH0 = 0x8C;
sfr TL1 = 0x8B;
sfr TH1 = 0x8D;

sfr ADC_CONTR = 0xBC;
sfr ADC_RES = 0xBD;
sfr ADC_RESL = 0xBE;
sfr ADCCFG = 0xDE;
sfr ADCTIM = 0xFE;// 需通过P_SW2使能XFR访问

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

sfr P_SW1 = 0xA2;
sfr P_SW2 = 0xBA;

// 位定义
sbit P34 = P3^4;
sbit P33 = P3^3;
sbit P32 = P3^2;
sbit P31 = P3^1;
sbit P30 = P3^0;

// 中断使能
#define EA1
#define ES1
#define ET0 1
#define ET1 1

#endif
</code></pre>
<hr />
<h3>使用说明</h3>
<p><strong>1. 编译与下载</strong></p>
<ul>
<li>使用 Keil C51 编译,芯片选择 STC8G1K08</li>
<li>使用 STC-ISP 下载软件,选择内部IRC时钟 <strong>11.0592MHz</strong></li>
<li>EEPROM 大小设置为 <strong>4K</strong>(默认)</li>
</ul>
<p><strong>2. 串口通信参数</strong></p>
<ul>
<li>波特率:<strong>115200</strong></li>
<li>数据位:<strong>8</strong></li>
<li>停止位:<strong>1</strong></li>
<li>校验位:<strong>无</strong></li>
</ul>
<p><strong>3. 指令表</strong></p>
<table>
<thead>
<tr>
<th align="center">指令</th>
<th align="left">功能</th>
<th align="left">示例响应</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><code>V</code></td>
<td align="left">读取电源电压</td>
<td align="left"><code>VCC=5.01V</code></td>
</tr>
<tr>
<td align="center"><code>0</code>~<code>9</code></td>
<td align="left">设置亮度10%~100%</td>
<td align="left"><code>Duty: 50%</code></td>
</tr>
<tr>
<td align="center"><code>+</code></td>
<td align="left">亮度+10%</td>
<td align="left"><code>Duty: 60%</code></td>
</tr>
<tr>
<td align="center"><code>-</code></td>
<td align="left">亮度-10%</td>
<td align="left"><code>Duty: 40%</code></td>
</tr>
<tr>
<td align="center"><code>S</code></td>
<td align="left">保存亮度到EEPROM</td>
<td align="left"><code>Saved: 50%</code></td>
</tr>
<tr>
<td align="center"><code>R</code></td>
<td align="left">从EEPROM恢复亮度</td>
<td align="left"><code>Restored: 50%</code></td>
</tr>
</tbody>
</table>
<p><strong>4. 低功耗说明</strong></p>
<ul>
<li>无操作约 <strong>30秒</strong> 后自动进入 Stop 掉电模式</li>
<li>串口发送任意数据(RxD下降沿)即可唤醒芯片</li>
<li>唤醒后自动恢复 PWM 输出和 ADC 功能</li>
</ul>
<p><strong>5. 硬件连接</strong></p>
<pre><code>STC8G1K08 SOP8
┌──────────┐
│ 1:P3.4 ──┤─── LED指示灯 (串联1kΩ电阻到GND)
│ 2:P3.3   │
│ 3:P3.2   │
│ 4:P3.1 ──┤─── TXD → USB转串口RXD
│ 5:P3.0 ──┤─── RXD → USB转串口TXD
│ 6:P5.4   │─── RST (接10kΩ上拉到VCC)
│ 7:VCC ───┤─── +5V
│ 8:GND ───┤─── GND
└──────────┘
</code></pre>
<hr />
<h3>注意事项</h3>
<ol>
<li><strong>PCA/PWM问题</strong>:STC8G1K08-8Pin系列(SOP8封装)手册中PCA功能列为空,无硬件PWM。代码中使用<strong>定时器0中断模拟PWM</strong>,频率约1kHz,100级占空比调节。如需硬件PWM,建议选用 <strong>STC8G1K08A-8Pin系列</strong>(有PCA)或 <strong>STC8G1K08-TSSOP20</strong>(有增强型PWM)。</li>
<li><strong>ADC测量电压</strong>:使用内部1.19V参考信号源(ADC通道15)反推VCC,公式为 <span class="language-math">​V_{CC} = 1.19V \times 1024 / ADC_{BGV}</span>。</li>
<li><strong>EEPROM操作</strong>:写操作前必须先擦除扇区(512字节),建议将亮度值单独放在一个扇区起始地址。</li>
<li><strong>掉电唤醒</strong>:串口RxD上的下降沿可唤醒掉电模式,需保持REN=1。唤醒后需重新配置外设寄存器。</li>
</ol>
<p>如需进一步调整或补充功能,请告知!</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC8G1K08%20ADC%20%E4%B8%B2%E5%8F%A3%20%E6%8C%87%E4%BB%A4%20%E7%94%B5%E6%BA%90%E7%94%B5%E5%8E%8B&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC8G1K08 ADC 串口 指令 电源电压&quot;</a></p>
<p>⏱️ 查找用时 49.0s | 总回答用时 105.2s</p>

芯启元 发表于 2026-7-3 15:13:17

STC【AI 助手】帮 用STC8A8K64D4 写一套完整工程演示程序,包含:
1.独立按键扫描,短按切换 LED 流水灯模式,长按保存参数;
2.ADC 采集可调电位器电压,定时串口打印电压数值;
3.EEPROM 存储灯光速度参数,上电自动读取恢复配置。

芯启元 发表于 2026-7-3 15:13:32

<p>信息已经足够,我来编写完整的工程演示程序。以下是基于 STC8A8K64D4 的完整工程代码:</p>
<hr />
<h2>STC8A8K64D4 完整工程演示程序</h2>
<h3>一、硬件引脚分配</h3>
<table>
<thead>
<tr>
<th align="left">功能</th>
<th align="left">引脚</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">LED流水灯</td>
<td align="left">P2.0~P2.7</td>
<td align="left">8个LED,推挽输出</td>
</tr>
<tr>
<td align="left">独立按键</td>
<td align="left">P3.2 (INT0)</td>
<td align="left">按键输入,高阻输入,外部中断下降沿触发</td>
</tr>
<tr>
<td align="left">ADC输入</td>
<td align="left">P1.0 (ADC0)</td>
<td align="left">可调电位器电压采样</td>
</tr>
<tr>
<td align="left">串口1 TXD</td>
<td align="left">P3.1</td>
<td align="left">串口打印</td>
</tr>
<tr>
<td align="left">串口1 RXD</td>
<td align="left">P3.0</td>
<td align="left">串口接收(本工程未使用接收中断)</td>
</tr>
</tbody>
</table>
<h3>二、完整工程代码(main.c)</h3>
<pre><code class="language-c">/******************************************************************************
* 文件名:main.c
* 芯片型号:STC8A8K64D4
* 系统时钟:22.1184MHz(内部IRC)
* 功能描述:
*   1. 独立按键短按切换LED流水灯模式,长按保存参数到EEPROM
*   2. ADC采集可调电位器电压,定时串口打印电压数值
*   3. EEPROM存储灯光速度参数,上电自动读取恢复配置
* 串口参数:115200, 8N1
******************************************************************************/

#include &quot;stc8a8k64d4.h&quot;
#include &lt;stdio.h&gt;

/**************************** 用户宏定义 *************************************/
#define MAIN_Fosc      22118400L      // 系统时钟频率 22.1184MHz
#define BAUD_RATE      115200L      // 串口波特率

// EEPROM 地址定义(使用IAP方式,EEPROM从后向前规划,物理地址F000h~FFFFh)
// IAP方式访问时地址从0000h开始
#define EEPROM_ADDR_SPEED0x0000       // 存储速度参数的EEPROM地址(1字节)

// 按键时间定义(单位:ms)
#define KEY_SHORT_MS       50         // 短按消抖时间
#define KEY_LONG_MS      1000         // 长按判定时间

// LED流水灯模式
#define LED_MODE_LEFT      0            // 从左到右
#define LED_MODE_RIGHT   1            // 从右到左
#define LED_MODE_BREATH    2            // 呼吸灯(PWM模拟)
#define LED_MODE_DOUBLE    3            // 两边向中间
#define LED_MODE_MAX       4            // 模式总数

/**************************** 全局变量 ***************************************/
bitB_1ms;                           // 1ms时基标志
bitB_KeyPress;                        // 按键按下标志
bitB_KeyLongPress;                  // 按键长按标志
bitB_AdcDone;                         // ADC转换完成标志

u8   Key_cnt;                           // 按键按下计时(ms)
u8   Led_mode;                        // 当前LED模式
u8   Led_speed;                         // LED速度参数(1~10,默认5)
u8   Led_index;                         // LED流水灯当前索引
u16Led_timer;                         // LED切换计时器
u16Adc_result;                        // ADC原始值(12位)
u16Adc_timer;                         // ADC打印计时器

/**************************** 函数声明 ***************************************/
void delay_ms(u16 ms);
void GPIO_config(void);
void Timer0_config(void);
void UART1_config(void);
void ADC_config(void);
void EEPROM_WriteByte(u16 addr, u8 dat);
u8   EEPROM_ReadByte(u16 addr);
void EEPROM_EraseSector(u16 addr);
void Key_Scan(void);
void Led_Process(void);
void Adc_Process(void);
void Print_AdcValue(void);
void PrintString(u8 *p);

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

    // 关闭所有中断,进行初始化
    EA = 0;

    // 配置系统时钟为内部IRC 22.1184MHz
    // STC8A8K64D4上电默认使用内部IRC,无需额外配置

    GPIO_config();          // 配置I/O口
    Timer0_config();      // 配置定时器0,产生1ms中断
    UART1_config();         // 配置串口1,115200bps
    ADC_config();         // 配置ADC

    // 从EEPROM读取保存的速度参数
    Led_speed = EEPROM_ReadByte(EEPROM_ADDR_SPEED);
    // 检查数据有效性(范围1~10)
    if((Led_speed &lt; 1) || (Led_speed &gt; 10))
    {
      Led_speed = 5;      // 无效数据恢复默认值
    }

    // 初始化LED模式
    Led_mode = 0;
    Led_index = 0;
    Led_timer = 0;
    Adc_timer = 0;

    // 串口打印启动信息
    PrintString(&quot;STC8A8K64D4 Demo Start!\r\n&quot;);
    PrintString(&quot;LED Speed: &quot;);
    SBUF = Led_speed + '0';
    while(!TI);
    TI = 0;
    PrintString(&quot;\r\n&quot;);

    // 开启总中断
    EA = 1;

    // 启动ADC首次转换
    ADC_CONTR |= 0x40;      // 启动ADC转换

    // 主循环
    while(1)
    {
      // 1ms时基处理
      if(B_1ms)
      {
            B_1ms = 0;

            // 按键扫描(每1ms调用一次)
            Key_Scan();

            // LED流水灯处理
            Led_Process();

            // ADC定时打印(每500ms打印一次)
            Adc_timer++;
            if(Adc_timer &gt;= 500)
            {
                Adc_timer = 0;
                Print_AdcValue();
            }
      }

      // ADC转换完成处理
      if(B_AdcDone)
      {
            B_AdcDone = 0;
            Adc_Process();
      }

      // 按键长按保存参数
      if(B_KeyLongPress)
      {
            B_KeyLongPress = 0;

            // 关闭总中断,保证EEPROM操作的原子性
            EA = 0;
            EEPROM_EraseSector(EEPROM_ADDR_SPEED);// 擦除扇区
            EEPROM_WriteByte(EEPROM_ADDR_SPEED, Led_speed);// 写入速度参数
            EA = 1;

            PrintString(&quot;Param Saved! Speed=&quot;);
            SBUF = Led_speed + '0';
            while(!TI);
            TI = 0;
            PrintString(&quot;\r\n&quot;);
      }
    }
}

/**************************** GPIO配置 ***************************************/
void GPIO_config(void)
{
    // P2口:推挽输出,驱动LED
    P2M1 = 0x00;
    P2M0 = 0xFF;    // P2.0~P2.7 推挽输出

    // P3.2:高阻输入,独立按键
    P3M1 |= 0x04;   // P3.2 高阻输入
    P3M0 &amp;= ~0x04;

    // P3.1:推挽输出,串口TXD
    P3M1 &amp;= ~0x02;
    P3M0 |= 0x02;   // P3.1 推挽输出

    // P3.0:高阻输入,串口RXD
    P3M1 |= 0x01;
    P3M0 &amp;= ~0x01;

    // P1.0:高阻输入,ADC输入
    P1M1 |= 0x01;
    P1M0 &amp;= ~0x01;

    // 关闭所有LED(低电平点亮)
    P2 = 0xFF;
}

/**************************** 定时器0配置 ************************************/
// 定时器0:16位自动重装载模式,1T模式
// 定时周期 = (65536 - 重载值) / (MAIN_Fosc / 1)
// 目标:1ms
// 重载值 = 65536 - MAIN_Fosc / 1000
// 22.1184MHz -&gt; 重载值 = 65536 - 22118 = 43418 = 0xA99A
void Timer0_config(void)
{
    AUXR |= 0x80;       // T0x12 = 1,定时器0 1T模式
    TMOD &amp;= 0xF0;       // T0_M1=0, T0_M0=0,16位自动重装载模式
    TL0 = 0x9A;         // 低字节
    TH0 = 0xA9;         // 高字节 (0xA99A)
    TR0 = 1;            // 启动定时器0
    ET0 = 1;            // 使能定时器0中断
}

/**************************** 串口1配置 **************************************/
// 串口1:模式1(8位UART),波特率115200
// 使用定时器2作为波特率发生器,1T模式
// 查手册:22.1184MHz, 115200bps, 定时器2 1T模式 -&gt; 重载值 = 0xFFD0
void UART1_config(void)
{
    // 配置串口1引脚为默认 P3.0(RxD), P3.1(TxD)
    // P_SW1 &amp;= 0x3F;// S1_S = 00,默认

    // 配置定时器2为波特率发生器
    AUXR &amp;= ~0x10;      // T2x12 = 0,12T模式... 不对,查表用1T模式
    // 查手册表格:22.1184MHz, 115200, 定时器2 1T模式 -&gt; 重载值 FFD0H
    // 但实际计算:波特率 = MAIN_Fosc / 4 / (65536 - 重载值)
    // 115200 = 22118400 / 4 / (65536 - RL)
    // RL = 65536 - 22118400 / 4 / 115200 = 65536 - 48 = 65488 = 0xFFD0
    // 所以用1T模式,T2x12=1
    AUXR |= 0x10;       // T2x12 = 1,1T模式
    T2L = 0xD0;         // 低字节
    T2H = 0xFF;         // 高字节 (0xFFD0)
    AUXR |= 0x04;       // T2R = 1,启动定时器2

    // 配置串口1
    SCON = 0x50;      // SM0=0, SM1=1 (模式1), REN=1 (允许接收)
    // 使用定时器2作为波特率发生器
    AUXR |= 0x01;       // S1ST2 = 1,串口1选择定时器2作为波特率发生器
    ES = 1;             // 使能串口1中断
}

/**************************** ADC配置 ****************************************/
// ADC:12位,右对齐,通道0 (P1.0)
// ADC时钟 = SYSclk / 2 / (SPEED+1)
// 设置SPEED=15 -&gt; ADC时钟 = 22.1184M / 2 / 16 ≈ 691kHz
void ADC_config(void)
{
    ADCTIM = 0x2A;      // CSSETUP=0, CSHOLD=01, SMPDUTY=01010 (默认值)
    ADCCFG = 0x2F;      // RESFMT=1 (右对齐), SPEED=15
    ADC_CONTR = 0x80;   // ADC_POWER=1, 开启ADC电源, ADC_CHS=0000 (通道0)
    EADC = 1;         // 使能ADC中断
    delay_ms(1);      // 等待ADC电源稳定
}

/**************************** EEPROM操作函数 *********************************/
// 写1字节到EEPROM(注意:写之前目标地址必须为FFH,否则需先擦除)
void EEPROM_WriteByte(u16 addr, u8 dat)
{
    IAP_TPS = 22;               // 22.1184MHz -&gt; IAP_TPS = 22
    IAP_ADDRH = (u8)(addr &gt;&gt; 8);
    IAP_ADDRL = (u8)(addr);
    IAP_DATA = dat;
    IAP_CMD = 0x02;             // 写命令
    IAP_CONTR = 0x80;         // IAPEN=1
    IAP_TRIG = 0x5A;            // 触发序列
    IAP_TRIG = 0xA5;
    // 等待操作完成(硬件自动完成,CPU暂停)
    IAP_CONTR = 0x00;         // 关闭IAP功能
}

// 从EEPROM读取1字节
u8 EEPROM_ReadByte(u16 addr)
{
    u8 dat;
    IAP_TPS = 22;
    IAP_ADDRH = (u8)(addr &gt;&gt; 8);
    IAP_ADDRL = (u8)(addr);
    IAP_CMD = 0x01;             // 读命令
    IAP_CONTR = 0x80;         // IAPEN=1
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    dat = IAP_DATA;
    IAP_CONTR = 0x00;         // 关闭IAP功能
    return dat;
}

// 擦除EEPROM扇区(512字节)
void EEPROM_EraseSector(u16 addr)
{
    IAP_TPS = 22;
    IAP_ADDRH = (u8)(addr &gt;&gt; 8);
    IAP_ADDRL = (u8)(addr);
    IAP_CMD = 0x03;             // 擦除命令
    IAP_CONTR = 0x80;         // IAPEN=1
    IAP_TRIG = 0x5A;
    IAP_TRIG = 0xA5;
    IAP_CONTR = 0x00;         // 关闭IAP功能
}

/**************************** 按键扫描 ***************************************/
// 按键接在P3.2,低电平表示按下
// 短按:按下时间 &lt; 1s,切换LED模式
// 长按:按下时间 &gt;= 1s,保存参数
void Key_Scan(void)
{
    if(P32 == 0)                // 检测到按键按下
    {
      Key_cnt++;
      if(Key_cnt &gt;= KEY_LONG_MS)      // 长按判定
      {
            Key_cnt = KEY_LONG_MS;      // 防止溢出
            if(!B_KeyLongPress)
            {
                B_KeyLongPress = 1;   // 置长按标志
            }
      }
    }
    else                        // 按键释放
    {
      if(Key_cnt &gt; KEY_SHORT_MS &amp;&amp; Key_cnt &lt; KEY_LONG_MS)// 短按
      {
            Led_mode++;
            if(Led_mode &gt;= LED_MODE_MAX)
            {
                Led_mode = 0;
            }
            Led_index = 0;      // 重置LED索引
            PrintString(&quot;Mode: &quot;);
            SBUF = Led_mode + '0';
            while(!TI);
            TI = 0;
            PrintString(&quot;\r\n&quot;);
      }
      Key_cnt = 0;            // 清零计时
    }
}

/**************************** LED流水灯处理 **********************************/
void Led_Process(void)
{
    u8 led_pattern;

    Led_timer++;
    // 根据速度参数计算切换间隔:速度1最慢(500ms),速度10最快(50ms)
    if(Led_timer &lt; (u16)(550 - Led_speed * 50))
    {
      return;
    }
    Led_timer = 0;

    switch(Led_mode)
    {
      case LED_MODE_LEFT:   // 从左到右
            led_pattern = (u8)(~(0x01 &lt;&lt; Led_index));
            Led_index++;
            if(Led_index &gt;= 8) Led_index = 0;
            break;

      case LED_MODE_RIGHT:    // 从右到左
            led_pattern = (u8)(~(0x80 &gt;&gt; Led_index));
            Led_index++;
            if(Led_index &gt;= 8) Led_index = 0;
            break;

      case LED_MODE_BREATH:   // 呼吸效果(简化:两个LED交替闪烁)
            if(Led_index == 0)
            {
                led_pattern = ~0x55;    // 01010101
                Led_index = 1;
            }
            else
            {
                led_pattern = ~0xAA;    // 10101010
                Led_index = 0;
            }
            break;

      case LED_MODE_DOUBLE:   // 两边向中间
      {
            u8 i;
            led_pattern = 0xFF;
            for(i = 0; i &lt;= Led_index; i++)
            {
                led_pattern &amp;= (u8)(~(0x01 &lt;&lt; i));       // 左边
                led_pattern &amp;= (u8)(~(0x80 &gt;&gt; i));       // 右边
            }
            Led_index++;
            if(Led_index &gt;= 4) Led_index = 0;
            break;
      }

      default:
            led_pattern = 0xFF;
            break;
    }

    P2 = led_pattern;
}

/**************************** ADC处理 ****************************************/
void Adc_Process(void)
{
    // 读取ADC结果(右对齐格式)
    Adc_result = ((u16)ADC_RES &lt;&lt; 8) | ADC_RESL;

    // 启动下一次ADC转换
    ADC_CONTR |= 0x40;
}

/**************************** 串口打印ADC值 **********************************/
void Print_AdcValue(void)
{
    u16 voltage_x100;   // 电压值×100,保留两位小数
    u8int_part, frac_part;

    // ADC参考电压为VCC(假设3.3V),12位ADC
    // 电压(mV) = Adc_result * 3300 / 4096
    // 电压×100 = Adc_result * 3300 * 100 / 4096
    // 简化:电压×100 ≈ Adc_result * 330000 / 4096
    voltage_x100 = (u16)((u32)Adc_result * 330000UL / 4096UL);

    int_part= (u8)(voltage_x100 / 100);
    frac_part = (u8)(voltage_x100 % 100);

    // 打印 &quot;ADC: X.XX V\r\n&quot;
    PrintString(&quot;ADC: &quot;);
    SBUF = int_part + '0';
    while(!TI); TI = 0;
    SBUF = '.';
    while(!TI); TI = 0;
    SBUF = (frac_part / 10) + '0';
    while(!TI); TI = 0;
    SBUF = (frac_part % 10) + '0';
    while(!TI); TI = 0;
    PrintString(&quot; V\r\n&quot;);
}

/**************************** 串口打印字符串 *********************************/
void PrintString(u8 *p)
{
    while(*p)
    {
      SBUF = *p;
      while(!TI);
      TI = 0;
      p++;
    }
}

/**************************** 软件延时 ***************************************/
void delay_ms(u16 ms)
{
    u16 i;
    for(i = 0; i &lt; ms; i++)
    {
      // 粗略延时1ms(22.1184MHz下约需22118个时钟周期)
      u16 j = MAIN_Fosc / 13000;
      while(j--);
    }
}

/**************************** 定时器0中断服务 ********************************/
void Timer0_ISR(void) interrupt 1
{
    B_1ms = 1;          // 置1ms标志
}

/**************************** 串口1中断服务 **********************************/
void UART1_ISR(void) interrupt 4
{
    if(RI)
    {
      RI = 0;         // 清除接收中断标志(本工程未使用接收)
    }
    if(TI)
    {
      TI = 0;         // 清除发送中断标志
    }
}

/**************************** ADC中断服务 ************************************/
void ADC_ISR(void) interrupt 5
{
    ADC_CONTR &amp;= ~0x20; // 清除ADC_FLAG
    B_AdcDone = 1;      // 置ADC完成标志
}
</code></pre>
<h3>三、工程文件清单</h3>
<table>
<thead>
<tr>
<th align="left">文件</th>
<th align="left">说明</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><code>main.c</code></td>
<td align="left">主程序代码(如上)</td>
</tr>
<tr>
<td align="left"><code>stc8a8k64d4.h</code></td>
<td align="left">STC官方头文件(从ISP软件中获取)</td>
</tr>
<tr>
<td align="left"><code>STARTUP.A51</code></td>
<td align="left">Keil启动文件(默认即可)</td>
</tr>
</tbody>
</table>
<h3>四、Keil工程配置要点</h3>
<ol>
<li><strong>芯片选择</strong>:在Keil中选择 <code>STC8A8K64D4</code>(需安装STC器件库)</li>
<li><strong>晶振频率</strong>:在Target选项中设置 <code>Xtal(MHz)</code> 为 <code>22.1184</code></li>
<li><strong>Memory Model</strong>:建议选择 <code>Large: variables in XDATA</code></li>
<li><strong>Code Rom Size</strong>:选择 <code>Large: 64K program</code></li>
<li><strong>生成HEX文件</strong>:勾选 &quot;Create HEX File&quot;</li>
</ol>
<h3>五、ISP下载设置</h3>
<p>使用 <strong>STC-ISP(AIapp-ISP)</strong> 下载软件:</p>
<ol>
<li>选择型号:<code>STC8A8K64D4</code></li>
<li>设置IRC频率:<code>22.1184MHz</code></li>
<li><strong>EEPROM设置</strong>:在下载软件中设置EEPROM大小为 <strong>1KB</strong>(占用最后1KB Flash空间,IAP地址范围 0000H<del>03FFH,物理地址 FC00H</del>FFFFH)</li>
<li>勾选 &quot;下次下载用户程序时擦除EEPROM区&quot;(首次下载时勾选)</li>
<li>选择HEX文件,点击下载</li>
</ol>
<h3>六、功能说明</h3>
<table>
<thead>
<tr>
<th align="left">功能</th>
<th align="left">操作</th>
<th align="left">现象</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left"><strong>LED流水灯</strong></td>
<td align="left">上电自动运行</td>
<td align="left">8个LED按当前模式循环显示</td>
</tr>
<tr>
<td align="left"><strong>切换模式</strong></td>
<td align="left">短按按键(&lt;1s)</td>
<td align="left">依次切换:左→右→右→左→呼吸→两边向中间→循环</td>
</tr>
<tr>
<td align="left"><strong>保存速度</strong></td>
<td align="left">长按按键(≥1s)</td>
<td align="left">串口打印 &quot;Param Saved! Speed=X&quot;,参数写入EEPROM</td>
</tr>
<tr>
<td align="left"><strong>ADC电压打印</strong></td>
<td align="left">自动每500ms</td>
<td align="left">串口打印 &quot;ADC: X.XX V&quot;</td>
</tr>
<tr>
<td align="left"><strong>上电恢复</strong></td>
<td align="left">重新上电</td>
<td align="left">从EEPROM读取上次保存的速度参数</td>
</tr>
</tbody>
</table>
<h3>七、串口助手配置</h3>
<table>
<thead>
<tr>
<th align="left">参数</th>
<th align="left">值</th>
</tr>
</thead>
<tbody>
<tr>
<td align="left">波特率</td>
<td align="left">115200</td>
</tr>
<tr>
<td align="left">数据位</td>
<td align="left">8</td>
</tr>
<tr>
<td align="left">停止位</td>
<td align="left">1</td>
</tr>
<tr>
<td align="left">校验位</td>
<td align="left">无</td>
</tr>
<tr>
<td align="left">流控制</td>
<td align="left">无</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注</strong>:以上代码基于 STC8A8K64D4 数据手册(章节:I/O口、定时器、串口通信、ADC模数转换、IAP/EEPROM)编写。实际使用时请根据具体硬件连接调整引脚定义。如需修改LED点亮逻辑(高电平/低电平),调整 <code>P2 = led_pattern</code> 处的取反逻辑即可。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC%E3%80%90AI%20%E5%8A%A9%E6%89%8B%E3%80%91%E5%B8%AE%20%E7%94%A8%20STC8A8K64D4%20%E5%86%99%E4%B8%80%E5%A5%97%E5%AE%8C%E6%95%B4%E5%B7%A5%E7%A8%8B%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%20%E5%8C%85%E5%90%AB%EF%BC%9A%0A1.%E7%8B%AC%E7%AB%8B%E6%8C%89%E9%94%AE%E6%89%AB%E6%8F%8F%20%E7%9F%AD%E6%8C%89%E5%88%87%E6%8D%A2%20LED%20&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC【AI 助手】帮 用 STC8A8K64D4 写一套完整工程演示程序 包含:<br />
1.独立按键扫描 短按切换 LED &quot;</a></p>
<p>⏱️ 查找用时 13.2s | 总回答用时 57.6s</p>

芯逐光 发表于 2026-7-3 15:15:48



STC【AI 助手】帮 STC8G1K08A-36I-SOP8 写一个完整演示程序,要包含下面这些:
单按键短按切换 PWM 启停、长按调整 PWM 占空比,带软件防抖处理;
定时器中断做毫秒定时,周期性采集 ADC 电压;
电压低于设定阈值时自动关断 PWM 输出,做低压保护逻辑。


页: 60 61 62 63 64 65 66 67 68 69 [70] 71 72 73 74 75 76 77 78 79
查看完整版本: 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】