AI芯 发表于 2026-7-3 10:05:32

<p>以下是关于 <strong>STC8H1K08</strong> 的 EEPROM 地址及读写方法的完整说明,信息来源于《STC8H系列数据手册》第20章「IAP/EEPROM/DATA-FLASH」。</p>
<hr />
<h2>一、EEPROM 地址</h2>
<p>STC8H1K08 的 EEPROM 大小为 <strong>4K 字节</strong>,共 <strong>8 个扇区</strong>(每扇区 512 字节)。</p>
<table>
<thead>
<tr>
<th align="center">访问方式</th>
<th align="center">起始地址</th>
<th align="center">结束地址</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><strong>IAP 方式</strong>(读/写/擦除)</td>
<td align="center"><strong>0000h</strong></td>
<td align="center"><strong>0FFFh</strong></td>
</tr>
<tr>
<td align="center"><strong>MOVC 方式</strong>(仅读取)</td>
<td align="center"><strong>2000h</strong></td>
<td align="center"><strong>2FFFh</strong></td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>说明</strong>:IAP 方式直接使用物理地址 0000h~0FFFh 访问 EEPROM。MOVC 方式读取时,需要在物理地址基础上加上程序空间大小(8KB = 2000h)的偏移,即实际地址 = EEPROM物理地址 + 2000h。</p>
</blockquote>
<hr />
<h2>二、EEPROM 读写方法</h2>
<h3>1. 相关寄存器</h3>
<table>
<thead>
<tr>
<th align="center">寄存器</th>
<th align="center">地址</th>
<th align="left">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><strong>IAP_DATA</strong></td>
<td align="center">C2H</td>
<td align="left">读/写数据寄存器</td>
</tr>
<tr>
<td align="center"><strong>IAP_ADDRH</strong></td>
<td align="center">C3H</td>
<td align="left">地址高字节</td>
</tr>
<tr>
<td align="center"><strong>IAP_ADDRL</strong></td>
<td align="center">C4H</td>
<td align="left">地址低字节</td>
</tr>
<tr>
<td align="center"><strong>IAP_CMD</strong></td>
<td align="center">C5H</td>
<td align="left">命令寄存器(01=读,02=写,03=擦除)</td>
</tr>
<tr>
<td align="center"><strong>IAP_TRIG</strong></td>
<td align="center">C6H</td>
<td align="left">触发寄存器(先写5AH,再写A5H)</td>
</tr>
<tr>
<td align="center"><strong>IAP_CONTR</strong></td>
<td align="center">C7H</td>
<td align="left">控制寄存器(bit7 IAPEN=1 使能)</td>
</tr>
<tr>
<td align="center"><strong>IAP_TPS</strong></td>
<td align="center">F5H</td>
<td align="left">等待参数 = 系统频率(MHz)</td>
</tr>
</tbody>
</table>
<h3>2. 操作步骤</h3>
<p><strong>① 设置 IAP_TPS</strong><br />
<code>IAP_TPS = 系统工作频率(MHz)</code>,例如 11.0592MHz 则设为 11。</p>
<p><strong>② 使能 IAP</strong><br />
<code>IAP_CONTR = 0x80;</code>(IAPEN=1)</p>
<p><strong>③ 设置命令</strong></p>
<ul>
<li>读:<code>IAP_CMD = 1;</code></li>
<li>写:<code>IAP_CMD = 2;</code></li>
<li>擦除扇区:<code>IAP_CMD = 3;</code></li>
</ul>
<p><strong>④ 设置地址</strong><br />
<code>IAP_ADDRH = addr &gt;&gt; 8;</code><br />
<code>IAP_ADDRL = addr &amp; 0xFF;</code></p>
<p><strong>⑤ 写数据(仅写操作需要)</strong><br />
<code>IAP_DATA = dat;</code></p>
<p><strong>⑥ 触发操作(必须关中断)</strong></p>
<pre><code class="language-c">EA = 0;               // 关总中断
IAP_TRIG = 0x5A;      // 先写5AH
IAP_TRIG = 0xA5;      // 再写A5H
_nop_(); _nop_(); _nop_(); _nop_();
</code></pre>
<p><strong>⑦ 读取结果(仅读操作需要)</strong><br />
<code>dat = IAP_DATA;</code></p>
<p><strong>⑧ 关闭 IAP</strong></p>
<pre><code class="language-c">IAP_CONTR = 0;
IAP_CMD   = 0;
IAP_TRIG= 0;
IAP_ADDRH = 0x80;
IAP_ADDRL = 0;
</code></pre>
<p><strong>⑨ 开中断</strong><br />
<code>EA = 1;</code></p>
<hr />
<h3>3. 完整 C 语言示例代码</h3>
<p>以下代码以 11.0592MHz 为例,演示 EEPROM 的读、写、擦除操作(摘自手册范例程序):</p>
<pre><code class="language-c">#include &quot;stc8h.h&quot;
#include &quot;intrins.h&quot;

void IapIdle()
{
    IAP_CONTR = 0;          // 关闭IAP功能
    IAP_CMD = 0;            // 清除命令寄存器
    IAP_TRIG = 0;         // 清除触发寄存器
    IAP_ADDRH = 0x80;       // 将地址设置到非IAP区域
    IAP_ADDRL = 0;
}

char IapRead(int addr)
{
    char dat;
    EA = 0;               // 关闭总中断
    IAP_CONTR = 0x80;       // 使能IAP
    IAP_TPS = 11;         // 设置等待参数11MHz
    IAP_CMD = 1;            // 设置IAP读命令
    IAP_ADDRL = addr;       // 设置IAP低地址
    IAP_ADDRH = addr &gt;&gt; 8;// 设置IAP高地址
    IAP_TRIG = 0x5a;      // 写触发命令(0x5a)
    IAP_TRIG = 0xa5;      // 写触发命令(0xa5)
    _nop_(); _nop_(); _nop_(); _nop_();
    dat = IAP_DATA;         // 读IAP数据
    IapIdle();            // 关闭IAP功能
    EA = 1;               // 开总中断
    return dat;
}

void IapProgram(int addr, char dat)
{
    EA = 0;               // 关闭总中断
    IAP_CONTR = 0x80;       // 使能IAP
    IAP_TPS = 11;         // 设置等待参数11MHz
    IAP_CMD = 2;            // 设置IAP写命令
    IAP_ADDRL = addr;       // 设置IAP低地址
    IAP_ADDRH = addr &gt;&gt; 8;// 设置IAP高地址
    IAP_DATA = dat;         // 写IAP数据
    IAP_TRIG = 0x5a;      // 写触发命令(0x5a)
    IAP_TRIG = 0xa5;      // 写触发命令(0xa5)
    _nop_(); _nop_(); _nop_(); _nop_();
    IapIdle();            // 关闭IAP功能
    EA = 1;               // 开总中断
}

void IapErase(int addr)
{
    EA = 0;               // 关闭总中断
    IAP_CONTR = 0x80;       // 使能IAP
    IAP_TPS = 11;         // 设置等待参数11MHz
    IAP_CMD = 3;            // 设置IAP擦除命令
    IAP_ADDRL = addr;       // 设置IAP低地址
    IAP_ADDRH = addr &gt;&gt; 8;// 设置IAP高地址
    IAP_TRIG = 0x5a;      // 写触发命令(0x5a)
    IAP_TRIG = 0xa5;      // 写触发命令(0xa5)
    _nop_(); _nop_(); _nop_(); _nop_();
    IapIdle();            // 关闭IAP功能
    EA = 1;               // 开总中断
}

void main()
{
    P_SW2 |= 0x80;          // 使能访问XFR
    // 设置I/O口模式(略)

    IapErase(0x0400);                     // 擦除第2扇区(地址0400h)
    P0 = IapRead(0x0400);                   // 读,P0 = 0xFF
    IapProgram(0x0400, 0x12);               // 写入0x12
    P1 = IapRead(0x0400);                   // 读,P1 = 0x12
    while (1);
}
</code></pre>
<hr />
<h3>4. 重要注意事项</h3>
<ol>
<li><strong>写操作只能将 1 写为 0</strong>,若要将 0 改回 1,必须先执行扇区擦除(整扇区变为 FFh)。</li>
<li><strong>擦除以扇区为单位</strong>(512 字节),地址低 9 位无效。例如擦除地址 0400h 与 04FFh 效果相同,都是擦除 0400h~05FFh 扇区。</li>
<li><strong>触发序列必须连续执行</strong>,中间不可被中断打断,因此操作前需 <code>EA=0</code>,操作完成后 <code>EA=1</code>。</li>
<li><strong>IAP_TPS 必须正确设置</strong>,否则 EEPROM 操作时序会出错。<code>IAP_TPS = 系统频率(MHz)</code>,四舍五入取整。</li>
<li>如果主循环和中断中都有 EEPROM 操作,务必在触发序列前后关/开中断,确保操作的<strong>原子性</strong>。</li>
</ol>
<hr />
<p>以上信息均来自 <strong>《STC8H系列数据手册》第20章「IAP/EEPROM/DATA-FLASH」</strong>。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC8H1K08%E7%9A%84%20EEPROM%E5%9C%B0%E5%9D%80%E6%98%AF%E5%A4%9A%E5%B0%91%EF%BC%9F%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99%EF%BC%9F&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC8H1K08的 EEPROM地址是多少?如何读写?&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=16674&amp;highlight=STC8H1K08%E7%9A%84EEPROM%E5%9C%B0%E5%9D%80%E6%98%AF%E5%A4%9A%E5%B0%91%EF%BC%9F%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99%EF%BC%9F&amp;page=1&amp;extra=#pid156211">使用stc8h1k24这个芯片操作eeprom地址多少</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=8592&amp;highlight=STC8H1K08%E7%9A%84EEPROM%E5%9C%B0%E5%9D%80%E6%98%AF%E5%A4%9A%E5%B0%91%EF%BC%9F%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99%EF%BC%9F&amp;page=1&amp;extra=#pid81737">STC8H1K08 程序/EEPROM, 空间大小,地址 问题 | KEIL C51试用版</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=20241&amp;highlight=STC8H1K08%E7%9A%84EEPROM%E5%9C%B0%E5%9D%80%E6%98%AF%E5%A4%9A%E5%B0%91%EF%BC%9F%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99%EF%BC%9F&amp;page=1&amp;extra=#pid187442">关于stc8h1k08的eeprom的IAP访问和地址访问</a></li>
</ol>
<p>⏱️ 查找用时 6.8s | 总回答用时 25.2s</p>

浅夏星空 发表于 2026-7-3 10:06:13

帮 STC32G12K128自动写一个完整的演示程序,用于做汽车行车记录仪


浅夏星空 发表于 2026-7-3 10:06:30

<p>好的,我已经查阅了 STC32G12K128 手册中关于 USB-CDC、ADC、IAP/EEPROM、定时器、I/O口、看门狗、时钟管理等关键模块的详细资料。现在我来为您编写一个完整的汽车行车记录仪演示程序。</p>
<p>这个程序将模拟行车记录仪的核心功能,包括:</p>
<ol>
<li><strong>USB-CDC虚拟串口</strong> - 与上位机通信</li>
<li><strong>ADC采集</strong> - 模拟传感器数据采集(如加速度、电压等)</li>
<li><strong>EEPROM存储</strong> - 记录数据掉电保存</li>
<li><strong>定时器</strong> - 精确定时采样</li>
<li><strong>看门狗</strong> - 系统监控</li>
<li><strong>I/O控制</strong> - LED指示等</li>
</ol>
<p>以下是完整的演示程序:</p>
<hr />
<h2>STC32G12K128 汽车行车记录仪演示程序</h2>
<h3>文件结构</h3>
<pre><code>├── main.c          - 主程序
├── config.h      - 配置文件
├── usb.h         - USB头文件(使用官方库)
└── eeprom.h      - EEPROM操作头文件
</code></pre>
<h3>1. config.h - 配置文件</h3>
<pre><code class="language-c">#ifndef __CONFIG_H__
#define __CONFIG_H__

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

// 系统时钟频率定义(使用内部IRC 33.1776MHz)
#define MAIN_Fosc      33177600L

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

// 行车记录仪参数配置
#define SAMPLE_INTERVAL_MS      100   // 采样间隔(毫秒)
#define LOG_SECTOR_ADDR         0x0000// 日志存储起始扇区
#define LOG_ENTRY_SIZE          32      // 每条记录大小(字节)
#define LOG_SECTOR_SIZE         512   // 每扇区大小
#define LOG_ENTRIES_PER_SECTOR(LOG_SECTOR_SIZE / LOG_ENTRY_SIZE)// 每扇区记录数

// ADC通道定义
#define ADC_CH_VOLTAGE          0       // 通道0 - 电源电压监测 (P1.0)
#define ADC_CH_ACCEL_X          1       // 通道1 - 加速度X轴 (P1.1)
#define ADC_CH_ACCEL_Y          2       // 通道2 - 加速度Y轴 (P1.2)
#define ADC_CH_ACCEL_Z          3       // 通道3 - 加速度Z轴 (P1.3)
#define ADC_CH_TEMPERATURE      15      // 通道15 - 内部温度/参考电压

// I/O口定义
#define LED_RUN               P3_2    // 运行指示灯
#define LED_ALARM               P3_3    // 报警指示灯
#define LED_REC               P3_4    // 记录指示灯
#define KEY_MODE                P3_5    // 模式切换按键

// 看门狗超时时间(约2.1秒,主频33.1776MHz)
#define WDT_TIMEOUT_MS          2000

// 事件标志
#define EVT_TIMER_SAMPLE      (1 &lt;&lt; 0)    // 定时采样事件
#define EVT_USB_RX            (1 &lt;&lt; 1)    // USB接收事件
#define EVT_ALARM               (1 &lt;&lt; 2)    // 报警事件
#define EVT_SAVE_LOG            (1 &lt;&lt; 3)    // 保存日志事件

#endif
</code></pre>
<h3>2. eeprom.h - EEPROM操作头文件</h3>
<pre><code class="language-c">#ifndef __EEPROM_H__
#define __EEPROM_H__

#include &quot;config.h&quot;

// EEPROM操作函数声明
void IapIdle(void);
u8   IapReadByte(u32 addr);
void IapWriteByte(u32 addr, u8 dat);
void IapEraseSector(u32 addr);
void EEPROM_ReadN(u32 addr, u8 *pBuf, u16 len);
void EEPROM_WriteN(u32 addr, u8 *pBuf, u16 len);
void EEPROM_EraseSector(u32 addr);

#endif
</code></pre>
<h3>3. main.c - 主程序</h3>
<pre><code class="language-c">/**
* @file    main.c
* @brief   STC32G12K128 汽车行车记录仪演示程序
* @note    使用内部IRC 33.1776MHz,USB-CDC虚拟串口通信
*
* 功能说明:
* 1. USB-CDC虚拟串口与上位机通信(不停电下载)
* 2. ADC采集多路模拟信号(模拟传感器)
* 3. EEPROM存储行车日志(掉电保存)
* 4. 定时器精确定时采样
* 5. 看门狗系统监控
* 6. LED状态指示
*
* 硬件连接:
* - P3.0/P3.1: USB-DP/USB-DN (通过USB口连接电脑)
* - P1.0: ADC通道0 - 电源电压监测
* - P1.1: ADC通道1 - 加速度传感器X
* - P1.2: ADC通道2 - 加速度传感器Y
* - P1.3: ADC通道3 - 加速度传感器Z
* - P3.2: 运行指示灯LED (推挽输出)
* - P3.3: 报警指示灯LED (推挽输出)
* - P3.4: 记录指示灯LED (推挽输出)
* - P3.5: 模式按键输入 (高阻输入)
*/

#include &quot;config.h&quot;
#include &quot;eeprom.h&quot;

// USB库头文件(需从STC-ISP软件中获取)
#include &quot;stc32_stc8_usb.h&quot;

// ==================== 全局变量 ====================

// 事件标志
volatile u8 g_EventFlags = 0;

// 系统运行状态
volatile u8g_SysTick = 0;         // 系统节拍(每1ms增加)
volatile u16 g_SampleCount = 0;   // 采样计数
volatile u8g_WorkMode = 0;      // 工作模式:0-正常记录,1-回放,2-设置

// ADC采样数据
typedef struct {
    u16 voltage;      // 电源电压 (mV)
    u16 accel_x;      // 加速度X
    u16 accel_y;      // 加速度Y
    u16 accel_z;      // 加速度Z
    u16 temperature;    // 内部温度参考
    u32 timestamp;      // 时间戳
} LogEntry_t;

LogEntry_t g_CurrentSample;         // 当前采样数据
LogEntry_t xdata g_LogBuffer;// 日志缓冲区

// EEPROM写入指针
u16 g_EepromWriteAddr = 0;          // 当前写入地址
u8g_LogIndex = 0;               // 当前扇区内的记录索引

// USB相关
char *g_pUsbRxBuffer = NULL;      // USB接收缓冲区指针
u8   g_UsbRxLen = 0;               // USB接收数据长度

// ==================== 函数声明 ====================

void System_Init(void);
void GPIO_Init(void);
void Timer_Init(void);
void ADC_Init(void);
void WDT_Init(void);
void USB_Init(void);
void Delay_ms(u16 ms);

u16ADC_ReadChannel(u8 ch);
void ADC_ReadAllChannels(void);
void ProcessSampleData(void);
void SaveLogToEEPROM(void);
void SendLogToUSB(void);
void ProcessUSBCommand(void);
void CheckAlarm(void);
void UpdateLEDs(void);

// ==================== 系统初始化 ====================

void System_Init(void)
{
    // 使能访问扩展RAM区
    EAXFR = 1;

    // 设置程序代码等待参数为最快
    WTST = 0x00;

    // 设置外部数据总线速度为最快
    CKCON = 0x00;

    // 初始化各外设
    GPIO_Init();
    Timer_Init();
    ADC_Init();
    WDT_Init();

    // 初始化全局变量
    g_SysTick = 0;
    g_SampleCount = 0;
    g_WorkMode = 0;
    g_EepromWriteAddr = 0;
    g_LogIndex = 0;

    // 从EEPROM读取上次写入地址(如果已保存)
    // 此处简化处理,从0开始
}

// ==================== GPIO初始化 ====================

void GPIO_Init(void)
{
    // 所有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;

    // LED指示灯 - 推挽输出
    // P3.2 - 运行指示灯
    // P3.3 - 报警指示灯
    // P3.4 - 记录指示灯
    P3M0 |= (1 &lt;&lt; 2) | (1 &lt;&lt; 3) | (1 &lt;&lt; 4);
    P3M1 &amp;= ~((1 &lt;&lt; 2) | (1 &lt;&lt; 3) | (1 &lt;&lt; 4));

    // 按键输入 - 高阻输入
    // P3.5 - 模式按键
    P3M0 &amp;= ~(1 &lt;&lt; 5);
    P3M1 |= (1 &lt;&lt; 5);

    // ADC输入通道 - 高阻输入
    // P1.0~P1.3
    P1M0 &amp;= ~0x0F;
    P1M1 |= 0x0F;

    // 初始状态:所有LED关闭
    LED_RUN = 1;
    LED_ALARM = 1;
    LED_REC = 1;
}

// ==================== 定时器初始化 ====================

void Timer_Init(void)
{
    // 使用定时器0,16位自动重装载模式
    // 1T模式,产生1ms定时中断
    // 计算公式:定时时间 = (65536 - 重载值) / (MAIN_Fosc / (TM0PS+1))
    // 重载值 = 65536 - MAIN_Fosc / 1000
    // 33.1776MHz / 1000 = 33177.6 ≈ 33178
    // 65536 - 33178 = 32358 = 0x7E66

    TM0PS = 0;          // 预分频为1
    AUXR |= (1 &lt;&lt; 7);   // T0x12 = 1, 1T模式
    TMOD &amp;= 0xF0;       // 清除T0模式位
    TMOD |= 0x00;       // 模式0:16位自动重装载

    // 设置重载值(1ms定时)
    // 实际值需要根据主频精确计算
    // 这里使用33.1776MHz计算
    u16 reload = 65536 - (u16)(MAIN_Fosc / 1000);
    TL0 = reload &amp; 0xFF;
    TH0 = (reload &gt;&gt; 8) &amp; 0xFF;

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

// ==================== ADC初始化 ====================

void ADC_Init(void)
{
    // 配置ADC时序
    // ADCTIM: CSSETUP=0, CSHOLD=01(2), SMPDUTY=11111(31)
    ADCTIM = 0x3F;      // 设置ADC内部时序,采样时间最长

    // ADCCFG: 结果右对齐,ADC时钟 = SYSclk/2/16
    // 33.1776MHz / 2 / 16 ≈ 1.0368MHz (12位ADC最高800KHz,需要调整)
    // 改为 SPEED=15, 33.1776/2/16=1.0368MHz 略高
    // 使用 SPEED=20, 33.1776/2/21=790KHz 符合要求
    ADCCFG = 0x20 | 20; // 结果右对齐,时钟分频

    // 打开ADC电源
    ADC_POWER = 1;

    // 等待ADC电源稳定(约1ms)
    Delay_ms(1);
}

// ==================== 看门狗初始化 ====================

void WDT_Init(void)
{
    // 使能看门狗,预分频设为最大(约8.39秒@12MHz)
    // 33.1776MHz下,选择分频系数128
    // 溢出时间 ≈ 8.39 * 33.1776/12 ≈ 23.2秒(太大)
    // 选择分频系数16
    // 溢出时间 ≈ 0.524 * 33.1776/12 ≈ 1.45秒
    WDT_CONTR = 0x20 | 0x03;// EN_WDT=1, WDT_PS=011(16分频)

    // 清除看门狗(首次喂狗)
    WDT_CONTR |= 0x10;// CLR_WDT=1
}

// ==================== USB初始化 ====================

void USB_Init(void)
{
    // 使用USB库函数初始化
    // 注意:需要在ISP下载时选择&quot;USB CDC&quot;模式
    // 并使能&quot;使用内置USB&quot;

    // 设置USB时钟源为内部48MHz IRC
    IRC48MCR = 0x80;            // 启动内部48M IRC
    while (!(IRC48MCR &amp; 1));    // 等待时钟稳定

    IRCBAND |= 0x80;            // 选择IRC48M作为USB时钟源
    IRCBAND &amp;= ~0x40;

    // 调用USB库初始化函数
    usb_init();

    // 设置不停电下载命令
    set_usb_ispcmd(&quot;@STCISP#&quot;);
}

// ==================== 延时函数 ====================

void Delay_ms(u16 ms)
{
    u16 i, j;
    for (i = 0; i &lt; ms; i++)
    {
      // 粗略延时约1ms(主频33.1776MHz)
      for (j = 0; j &lt; (MAIN_Fosc / 13000); j++);
    }
}

// ==================== ADC读取函数 ====================

u16 ADC_ReadChannel(u8 ch)
{
    u16 result;

    // 选择ADC通道
    ADC_CONTR = (ADC_CONTR &amp; 0xF0) | ch;

    // 启动ADC转换
    ADC_START = 1;

    // 等待转换完成
    _nop_(); _nop_();
    while (!ADC_FLAG);

    // 清除标志
    ADC_FLAG = 0;

    // 读取结果(右对齐模式)
    result = ((u16)ADC_RES &lt;&lt; 8) | ADC_RESL;

    return result;
}

void ADC_ReadAllChannels(void)
{
    u16 bg_val;   // 内部参考电压ADC值
    u16 bg_mv;      // 内部参考电压(mV),从CHIPID读取

    // 读取内部1.19V参考信号源(通道15)
    bg_val = ADC_ReadChannel(ADC_CH_TEMPERATURE);

    // 从CHIPID读取内部参考电压值
    // CHIPID7和CHIPID8存储了内部参考电压值
    bg_mv = ((u16)CHIPID7 &lt;&lt; 8) | CHIPID8;

    // 读取各通道电压值
    // 使用内部参考反推外部电压:Vx = BGV / res_bg * res_x
    // 其中BGV为内部参考电压(mV),res_bg为通道15的ADC值,res_x为目标通道ADC值

    // 电源电压监测(通道0)
    u16 adc_val = ADC_ReadChannel(ADC_CH_VOLTAGE);
    g_CurrentSample.voltage = (u16)((u32)bg_mv * adc_val / bg_val);

    // 加速度X(通道1)
    adc_val = ADC_ReadChannel(ADC_CH_ACCEL_X);
    g_CurrentSample.accel_x = (u16)((u32)bg_mv * adc_val / bg_val);

    // 加速度Y(通道2)
    adc_val = ADC_ReadChannel(ADC_CH_ACCEL_Y);
    g_CurrentSample.accel_y = (u16)((u32)bg_mv * adc_val / bg_val);

    // 加速度Z(通道3)
    adc_val = ADC_ReadChannel(ADC_CH_ACCEL_Z);
    g_CurrentSample.accel_z = (u16)((u32)bg_mv * adc_val / bg_val);

    // 保存内部参考值
    g_CurrentSample.temperature = bg_val;

    // 时间戳(使用系统节拍)
    g_CurrentSample.timestamp = (u32)g_SysTick;
}

// ==================== 数据处理函数 ====================

void ProcessSampleData(void)
{
    // 将当前采样数据存入日志缓冲区
    LogEntry_t *pEntry = &amp;g_LogBuffer;

    pEntry-&gt;voltage   = g_CurrentSample.voltage;
    pEntry-&gt;accel_x   = g_CurrentSample.accel_x;
    pEntry-&gt;accel_y   = g_CurrentSample.accel_y;
    pEntry-&gt;accel_z   = g_CurrentSample.accel_z;
    pEntry-&gt;temperature = g_CurrentSample.temperature;
    pEntry-&gt;timestamp   = g_CurrentSample.timestamp;

    g_LogIndex++;

    // 如果缓冲区满了,保存到EEPROM
    if (g_LogIndex &gt;= LOG_ENTRIES_PER_SECTOR)
    {
      g_EventFlags |= EVT_SAVE_LOG;
    }

    // 检查是否需要报警(加速度异常)
    CheckAlarm();
}

void CheckAlarm(void)
{
    // 模拟加速度异常检测
    // 如果加速度值超出正常范围(假设正常范围1000~3000mV对应值)
    if ((g_CurrentSample.accel_x &gt; 3500) || (g_CurrentSample.accel_x &lt; 500) ||
      (g_CurrentSample.accel_y &gt; 3500) || (g_CurrentSample.accel_y &lt; 500) ||
      (g_CurrentSample.accel_z &gt; 3500) || (g_CurrentSample.accel_z &lt; 500))
    {
      g_EventFlags |= EVT_ALARM;
    }
}

// ==================== EEPROM操作函数 ====================

void IapIdle(void)
{
    IAP_CONTR = 0;         // 关闭IAP功能
    IAP_CMD = 0;         // 清除命令寄存器
    IAP_TRIG = 0;          // 清除触发寄存器
    IAP_ADDRE = 0x00;
    IAP_ADDRH = 0x00;
    IAP_ADDRL = 0x00;
}

u8 IapReadByte(u32 addr)
{
    u8 dat;

    EA = 0;                // 关闭总中断
    IAP_CONTR = 0x80;      // 使能IAP
    IAP_TPS = (u8)(MAIN_Fosc / 1000000L);// 设置等待参数
    IAP_CMD = 1;         // 设置IAP读命令
    IAP_ADDRL = (u8)(addr &amp; 0xFF);
    IAP_ADDRH = (u8)((addr &gt;&gt; 8) &amp; 0xFF);
    IAP_ADDRE = (u8)((addr &gt;&gt; 16) &amp; 0xFF);

    IAP_TRIG = 0x5A;       // 写触发命令
    IAP_TRIG = 0xA5;
    _nop_(); _nop_(); _nop_(); _nop_();

    dat = IAP_DATA;      // 读取数据
    IapIdle();             // 关闭IAP功能
    EA = 1;                // 开启总中断

    return dat;
}

void IapWriteByte(u32 addr, u8 dat)
{
    EA = 0;                // 关闭总中断
    IAP_CONTR = 0x80;      // 使能IAP
    IAP_TPS = (u8)(MAIN_Fosc / 1000000L);// 设置等待参数
    IAP_CMD = 2;         // 设置IAP写命令
    IAP_ADDRL = (u8)(addr &amp; 0xFF);
    IAP_ADDRH = (u8)((addr &gt;&gt; 8) &amp; 0xFF);
    IAP_ADDRE = (u8)((addr &gt;&gt; 16) &amp; 0xFF);
    IAP_DATA = dat;      // 写入数据

    IAP_TRIG = 0x5A;       // 写触发命令
    IAP_TRIG = 0xA5;
    _nop_(); _nop_(); _nop_(); _nop_();

    IapIdle();             // 关闭IAP功能
    EA = 1;                // 开启总中断
}

void IapEraseSector(u32 addr)
{
    EA = 0;                // 关闭总中断
    IAP_CONTR = 0x80;      // 使能IAP
    IAP_TPS = (u8)(MAIN_Fosc / 1000000L);// 设置等待参数
    IAP_CMD = 3;         // 设置IAP擦除命令
    IAP_ADDRL = (u8)(addr &amp; 0xFF);
    IAP_ADDRH = (u8)((addr &gt;&gt; 8) &amp; 0xFF);
    IAP_ADDRE = (u8)((addr &gt;&gt; 16) &amp; 0xFF);

    IAP_TRIG = 0x5A;       // 写触发命令
    IAP_TRIG = 0xA5;
    _nop_(); _nop_(); _nop_(); _nop_();

    IapIdle();             // 关闭IAP功能
    EA = 1;                // 开启总中断
}

void EEPROM_ReadN(u32 addr, u8 *pBuf, u16 len)
{
    u16 i;
    for (i = 0; i &lt; len; i++)
    {
      pBuf = IapReadByte(addr + i);
    }
}

void EEPROM_WriteN(u32 addr, u8 *pBuf, u16 len)
{
    u16 i;
    for (i = 0; i &lt; len; i++)
    {
      IapWriteByte(addr + i, pBuf);
    }
}

void EEPROM_EraseSector(u32 addr)
{
    IapEraseSector(addr);
}

// ==================== 保存日志到EEPROM ====================

void SaveLogToEEPROM(void)
{
    u32 sector_addr;
    u32 write_addr;
    u8 *pData;

    // 计算当前扇区地址
    sector_addr = (u32)g_EepromWriteAddr;

    // 擦除当前扇区
    EEPROM_EraseSector(sector_addr);

    // 将缓冲区数据写入EEPROM
    pData = (u8 *)g_LogBuffer;
    write_addr = sector_addr;
    EEPROM_WriteN(write_addr, pData, LOG_ENTRIES_PER_SECTOR * LOG_ENTRY_SIZE);

    // 更新写入地址到下一个扇区
    g_EepromWriteAddr += LOG_SECTOR_SIZE;
    if (g_EepromWriteAddr &gt;= (128 * LOG_SECTOR_SIZE))// 假设128个扇区
    {
      g_EepromWriteAddr = 0;// 循环覆盖
    }

    // 重置日志索引
    g_LogIndex = 0;

    // 记录指示灯闪烁
    LED_REC = !LED_REC;
}

// ==================== USB数据发送 ====================

void SendLogToUSB(void)
{
    u8 buf;
    u8 len;

    // 格式化当前采样数据为字符串
    len = (u8)sprintf(buf,
      &quot;TIME:%lu,V:%u,AX:%u,AY:%u,AZ:%u,TEMP:%u\r\n&quot;,
      g_CurrentSample.timestamp,
      g_CurrentSample.voltage,
      g_CurrentSample.accel_x,
      g_CurrentSample.accel_y,
      g_CurrentSample.accel_z,
      g_CurrentSample.temperature);

    // 通过USB-CDC发送数据
    USB_SendData(buf, len);
}

// ==================== USB命令处理 ====================

void ProcessUSBCommand(void)
{
    char *cmd = g_pUsbRxBuffer;
    u8 len = g_UsbRxLen;

    if (len == 0) return;

    // 解析命令
    if (cmd == 'R' || cmd == 'r')
    {
      // 读取EEPROM数据
      u32 addr = 0;
      u8 buf;
      u8 out_buf;
      
      // 读取第一条记录
      EEPROM_ReadN(addr, buf, LOG_ENTRY_SIZE);
      
      sprintf(out_buf, &quot;EEPROM: V=%u,AX=%u,AY=%u,AZ=%u\r\n&quot;,
            ((LogEntry_t *)buf)-&gt;voltage,
            ((LogEntry_t *)buf)-&gt;accel_x,
            ((LogEntry_t *)buf)-&gt;accel_y,
            ((LogEntry_t *)buf)-&gt;accel_z);
      
      USB_SendData(out_buf, (int)strlen(out_buf));
    }
    else if (cmd == 'E' || cmd == 'e')
    {
      // 擦除所有EEPROM
      u16 i;
      for (i = 0; i &lt; 128; i++)
      {
            EEPROM_EraseSector((u32)i * LOG_SECTOR_SIZE);
      }
      USB_SendData(&quot;EEPROM Erased!\r\n&quot;, 17);
    }
    else if (cmd == 'S' || cmd == 's')
    {
      // 发送当前状态
      u8 buf;
      sprintf(buf, &quot;Mode:%d,Sample:%u,Tick:%u,Addr:0x%04X\r\n&quot;,
            g_WorkMode, g_SampleCount, g_SysTick, g_EepromWriteAddr);
      USB_SendData(buf, (int)strlen(buf));
    }
    else if (cmd == 'H' || cmd == 'h')
    {
      // 帮助信息
      USB_SendData(&quot;Commands:\r\n&quot;, 12);
      USB_SendData(&quot;R - Read EEPROM\r\n&quot;, 18);
      USB_SendData(&quot;E - Erase EEPROM\r\n&quot;, 19);
      USB_SendData(&quot;S - Show Status\r\n&quot;, 18);
      USB_SendData(&quot;H - Help\r\n&quot;, 11);
    }

    // 处理完成,通知USB库接收下一包数据
    usb_OUT_done();
}

// ==================== LED更新 ====================

void UpdateLEDs(void)
{
    // 运行指示灯 - 系统节拍闪烁(1Hz)
    static u8 tick_counter = 0;
    tick_counter++;
    if (tick_counter &gt;= 500)// 500ms翻转
    {
      tick_counter = 0;
      LED_RUN = !LED_RUN;
    }

    // 报警指示灯 - 由报警事件控制
    if (g_EventFlags &amp; EVT_ALARM)
    {
      LED_ALARM = 0;// 点亮
    }
    else
    {
      LED_ALARM = 1;// 熄灭
    }
}

// ==================== 定时器0中断服务函数 ====================

void TM0_Isr(void) interrupt 1
{
    // 系统节拍增加
    g_SysTick++;

    // 每SAMPLE_INTERVAL_MS触发一次采样事件
    if ((g_SysTick % SAMPLE_INTERVAL_MS) == 0)
    {
      g_EventFlags |= EVT_TIMER_SAMPLE;
    }

    // 喂狗(每1ms喂一次)
    WDT_CONTR |= 0x10;// CLR_WDT=1
}

// ==================== USB中断服务函数 ====================

void USB_Isr(void) interrupt 25
{
    // USB库内部处理中断
    // 用户无需在此处添加代码
}

// ==================== 主函数 ====================

void main(void)
{
    // 系统初始化
    System_Init();

    // USB初始化
    USB_Init();

    // 使能总中断
    EA = 1;

    // 等待USB配置完成
    while (DeviceState != DEVSTATE_CONFIGURED)
    {
      // 等待主机配置USB设备
      Delay_ms(10);
    }

    // 发送启动信息
    USB_SendData(&quot;STC32G12K128 Car DVR Demo\r\n&quot;, 28);
    USB_SendData(&quot;System Ready!\r\n&quot;, 15);

    // 主循环
    while (1)
    {
      // 处理USB接收数据(查询方式)
      if (bUsbOutReady)
      {
            g_pUsbRxBuffer = (char *)UsbOutBuffer;
            g_UsbRxLen = OutNumber;
            g_EventFlags |= EVT_USB_RX;
      }
      
      // 处理事件
      if (g_EventFlags &amp; EVT_TIMER_SAMPLE)
      {
            g_EventFlags &amp;= ~EVT_TIMER_SAMPLE;
         
            // 读取所有ADC通道
            ADC_ReadAllChannels();
         
            // 处理采样数据
            ProcessSampleData();
         
            // 通过USB发送实时数据
            SendLogToUSB();
         
            g_SampleCount++;
      }
      
      if (g_EventFlags &amp; EVT_USB_RX)
      {
            g_EventFlags &amp;= ~EVT_USB_RX;
         
            // 处理USB命令
            ProcessUSBCommand();
      }
      
      if (g_EventFlags &amp; EVT_SAVE_LOG)
      {
            g_EventFlags &amp;= ~EVT_SAVE_LOG;
         
            // 保存日志到EEPROM
            SaveLogToEEPROM();
      }
      
      if (g_EventFlags &amp; EVT_ALARM)
      {
            // 报警处理(持续点亮报警灯,发送报警信息)
            static u8 alarm_sent = 0;
            if (!alarm_sent)
            {
                USB_SendData(&quot;ALARM! Abnormal acceleration detected!\r\n&quot;, 42);
                alarm_sent = 1;
            }
         
            // 延时后清除报警(模拟)
            // 实际应用中需要按键确认或条件解除
            if (g_SysTick % 5000 == 0)// 5秒后自动清除
            {
                g_EventFlags &amp;= ~EVT_ALARM;
                alarm_sent = 0;
                LED_ALARM = 1;// 关闭报警灯
            }
      }
      
      // 更新LED状态
      UpdateLEDs();
      
      // 检查按键(简单处理)
      if (KEY_MODE == 0)// 按键按下(低电平)
      {
            Delay_ms(20);   // 消抖
            if (KEY_MODE == 0)
            {
                // 切换工作模式
                g_WorkMode++;
                if (g_WorkMode &gt; 2) g_WorkMode = 0;
            
                u8 buf;
                sprintf(buf, &quot;Mode changed to: %d\r\n&quot;, g_WorkMode);
                USB_SendData(buf, (int)strlen(buf));
            
                // 等待按键释放
                while (KEY_MODE == 0);
            }
      }
      
      // 喂狗(主循环中再次喂狗,确保安全)
      WDT_CONTR |= 0x10;
    }
}
</code></pre>
<hr />
<h2>使用说明</h2>
<h3>1. 编译环境配置</h3>
<ul>
<li>使用 Keil C251 编译器</li>
<li>将 <code>stc32g.h</code> 头文件(从 STC-ISP 软件获取)放入项目目录</li>
<li>将 USB 库文件(<code>stc32_stc8_usb.h</code> 和 <code>ai_usb.h</code>)放入项目目录</li>
<li>在 STC-ISP 下载软件中选择:
<ul>
<li>型号:STC32G12K128</li>
<li>内部IRC频率:33.1776MHz</li>
<li>USB模式:CDC(虚拟串口)</li>
<li>使能&quot;使用内置USB&quot;</li>
</ul>
</li>
</ul>
<h3>2. 硬件连接</h3>
<table>
<thead>
<tr>
<th>引脚</th>
<th>功能</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td>P3.0</td>
<td>USB_DP</td>
<td>USB D+ 线</td>
</tr>
<tr>
<td>P3.1</td>
<td>USB_DN</td>
<td>USB D- 线</td>
</tr>
<tr>
<td>P1.0</td>
<td>ADC0</td>
<td>电源电压监测(分压后)</td>
</tr>
<tr>
<td>P1.1</td>
<td>ADC1</td>
<td>加速度传感器X轴</td>
</tr>
<tr>
<td>P1.2</td>
<td>ADC2</td>
<td>加速度传感器Y轴</td>
</tr>
<tr>
<td>P1.3</td>
<td>ADC3</td>
<td>加速度传感器Z轴</td>
</tr>
<tr>
<td>P3.2</td>
<td>LED_RUN</td>
<td>运行指示灯</td>
</tr>
<tr>
<td>P3.3</td>
<td>LED_ALARM</td>
<td>报警指示灯</td>
</tr>
<tr>
<td>P3.4</td>
<td>LED_REC</td>
<td>记录指示灯</td>
</tr>
<tr>
<td>P3.5</td>
<td>KEY_MODE</td>
<td>模式切换按键</td>
</tr>
</tbody>
</table>
<h3>3. 上位机命令</h3>
<p>通过串口助手(如STC-ISP的串口助手)发送以下命令:</p>
<table>
<thead>
<tr>
<th>命令</th>
<th>功能</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>R</code> 或 <code>r</code></td>
<td>读取EEPROM中的第一条记录</td>
</tr>
<tr>
<td><code>E</code> 或 <code>e</code></td>
<td>擦除所有EEPROM数据</td>
</tr>
<tr>
<td><code>S</code> 或 <code>s</code></td>
<td>显示系统状态</td>
</tr>
<tr>
<td><code>H</code> 或 <code>h</code></td>
<td>显示帮助信息</td>
</tr>
</tbody>
</table>
<h3>4. 程序功能说明</h3>
<ul>
<li><strong>数据采集</strong>:每100ms采集一次ADC数据(电压、加速度等)</li>
<li><strong>数据存储</strong>:每扇区满(16条记录)自动写入EEPROM</li>
<li><strong>异常检测</strong>:加速度值超出范围时触发报警</li>
<li><strong>USB通信</strong>:实时上传采样数据,支持命令交互</li>
<li><strong>看门狗</strong>:1ms喂一次,防止程序跑飞</li>
<li><strong>不停电下载</strong>:支持通过USB命令 <code>@STCISP#</code> 触发不停电下载</li>
</ul>
<hr />
<p><strong>注意</strong>:此程序为演示程序,实际行车记录仪产品还需要考虑:</p>
<ol>
<li>使用外部RTC提供精确时间戳</li>
<li>使用SD卡或更大容量存储</li>
<li>更复杂的碰撞检测算法</li>
<li>电源管理(掉电检测、低功耗模式)</li>
<li>更完善的数据校验和冗余备份机制</li>
</ol>
<p>如需进一步调整或增加功能,请告知具体需求。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC32G12K128%20%E8%A1%8C%E8%BD%A6%E8%AE%B0%E5%BD%95%E4%BB%AA%20%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%20%E7%A4%BA%E4%BE%8B%E4%BB%A3%E7%A0%81&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC32G12K128 行车记录仪 演示程序 示例代码&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%20STC32G12K128%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%81%9A%E6%B1%BD%E8%BD%A6%E8%A1%8C%E8%BD%A6%E8%AE%B0%E5%BD%95%E4%BB%AA&amp;page=65&amp;extra=#pid222111">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24648&amp;highlight=%E5%B8%AE%20STC32G12K128%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%81%9A%E6%B1%BD%E8%BD%A6%E8%A1%8C%E8%BD%A6%E8%AE%B0%E5%BD%95%E4%BB%AA&amp;page=1&amp;extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=7013&amp;highlight=%E5%B8%AE%20STC32G12K128%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E7%94%A8%E4%BA%8E%E5%81%9A%E6%B1%BD%E8%BD%A6%E8%A1%8C%E8%BD%A6%E8%AE%B0%E5%BD%95%E4%BB%AA&amp;page=1&amp;extra=#pid61838">STC32G12K128车规32位8051的应用场景</a></li>
</ol>
<p>⏱️ 查找用时 15.1s | 总回答用时 88.2s</p>

芯风起 发表于 2026-7-3 10:13:44


帮STC15H2K64S4 自动写一个完整的演示程序,用途:
驱动12864液晶屏

芯风起 发表于 2026-7-3 10:13:56

<p>好的,论坛搜索结果虽然多但未直接显示具体帖子内容。不过根据我掌握的标准 12864(KS0108/ST7920)驱动知识和 STC15H2K64S4 的手册信息,我可以为您编写一个完整的演示程序。下面开始编写。</p>
<hr />
<p>以下是为 <strong>STC15H2K64S4</strong> 编写的完整 12864 液晶屏驱动演示程序。本程序采用 <strong>8 位并行总线</strong>方式驱动 <strong>ST7920 控制器</strong>的 12864 液晶屏(市面上最常见的 12864 带中文字库模块),使用内部 IRC 时钟(默认频率,用户可在 ISP 下载时设置,建议 11.0592MHz 或 22.1184MHz)。</p>
<hr />
<h2>完整程序代码</h2>
<pre><code class="language-c">/******************************************************************************
* 文件名:main.c
* 芯片:STC15H2K64S4
* 功能:驱动12864液晶屏(ST7920控制器,8位并行接口)
* 时钟:内部IRC,建议11.0592MHz或22.1184MHz
* 接线:
*   LCD_RS(数据/指令)-&gt;P1.0
*   LCD_RW(读/写)      -&gt;P1.1
*   LCD_EN(使能)       -&gt;P1.2
*   LCD_CS(片选)       -&gt;P1.3(部分12864模块需要,如无片选可悬空)
*   LCD_D0~D7 (数据总线) -&gt;P0.0~P0.7
*   LCD_RST (复位)       -&gt;P1.4(可选,部分模块需要)
*   LCD_PSB (并/串选择)-&gt;接VCC (高电平=并行模式)
*   背光LED            -&gt;串接电阻接VCC/GND
******************************************************************************/

#include &quot;STC15H2K64S4.h&quot;
#include

/*==================== 用户引脚定义 ====================*/
// 可根据实际硬件接线修改
#define LCD_RS   P10    // 数据/指令选择 (0=指令, 1=数据)
#define LCD_RW   P11    // 读/写选择   (0=写, 1=读)
#define LCD_EN   P12    // 使能信号      (高电平有效)
#define LCD_CS   P13    // 片选          (部分模块需要)
#define LCD_RSTP14    // 复位          (低电平复位,可选)

#define LCD_DATA P0   // 8位数据总线

/*==================== 延时函数 ====================*/
/**********************************************
* 函数:delay_us
* 功能:微秒级延时(粗略,适用于11.0592MHz)
* 参数:us - 延时微秒数
**********************************************/
void delay_us(unsigned int us)
{
    unsigned int i;
    for(i = 0; i &lt; us; i++)
    {
      _nop_(); _nop_(); _nop_(); _nop_();
    }
}

/**********************************************
* 函数:delay_ms
* 功能:毫秒级延时
* 参数:ms - 延时毫秒数
**********************************************/
void delay_ms(unsigned int ms)
{
    unsigned int i, j;
    for(i = 0; i &lt; ms; i++)
      for(j = 0; j &lt; 1200; j++);// 约1ms @ 11.0592MHz
}

/*==================== 12864底层驱动函数 ====================*/
/**********************************************
* 函数:LCD_CheckBusy
* 功能:检查液晶是否忙
* 返回:0=空闲, 1=忙
**********************************************/
unsigned char LCD_CheckBusy(void)
{
    unsigned char busy;

    LCD_DATA = 0xFF;            // 数据口先写1,准备读取
    LCD_RS = 0;               // 指令模式
    LCD_RW = 1;               // 读模式
    _nop_();
    LCD_EN = 1;               // 使能
    _nop_(); _nop_();         // 等待数据稳定
    busy = (LCD_DATA &amp; 0x80);   // 读取忙标志 (DB7)
    LCD_EN = 0;               // 关闭使能

    return busy;
}

/**********************************************
* 函数:LCD_WriteCmd
* 功能:向液晶写入指令
* 参数:cmd - 指令字节
**********************************************/
void LCD_WriteCmd(unsigned char cmd)
{
    // 检查忙标志(部分模块可省略,用延时替代)
    // while(LCD_CheckBusy());

    LCD_RS = 0;               // 指令模式
    LCD_RW = 0;               // 写模式
    LCD_DATA = cmd;             // 写入指令
    _nop_();
    LCD_EN = 1;               // 产生使能脉冲
    _nop_(); _nop_(); _nop_(); _nop_();
    LCD_EN = 0;

    delay_us(5);                // 等待指令执行
}

/**********************************************
* 函数:LCD_WriteData
* 功能:向液晶写入数据
* 参数:dat - 数据字节
**********************************************/
void LCD_WriteData(unsigned char dat)
{
    // 检查忙标志(部分模块可省略,用延时替代)
    // while(LCD_CheckBusy());

    LCD_RS = 1;               // 数据模式
    LCD_RW = 0;               // 写模式
    LCD_DATA = dat;             // 写入数据
    _nop_();
    LCD_EN = 1;               // 产生使能脉冲
    _nop_(); _nop_(); _nop_(); _nop_();
    LCD_EN = 0;

    delay_us(5);                // 等待数据写入
}

/*==================== 12864功能函数 ====================*/
/**********************************************
* 函数:LCD_Init
* 功能:初始化12864液晶
**********************************************/
void LCD_Init(void)
{
    // 如果有复位引脚,先复位
    LCD_RST = 0;
    delay_ms(10);
    LCD_RST = 1;
    delay_ms(10);

    // 如果有片选引脚,使能
    LCD_CS = 1;

    delay_ms(50);               // 等待液晶上电稳定

    // ST7920 初始化序列
    LCD_WriteCmd(0x30);         // 基本指令集,8位数据总线
    delay_ms(5);
    LCD_WriteCmd(0x30);         // 重复一次确保稳定
    delay_ms(5);
    LCD_WriteCmd(0x0C);         // 显示开,光标关,闪烁关
    delay_ms(2);
    LCD_WriteCmd(0x01);         // 清屏
    delay_ms(10);
    LCD_WriteCmd(0x06);         // 光标右移,整屏不移动
    delay_ms(2);
}

/**********************************************
* 函数:LCD_Clear
* 功能:清屏
**********************************************/
void LCD_Clear(void)
{
    LCD_WriteCmd(0x01);         // 清屏指令
    delay_ms(10);
}

/**********************************************
* 函数:LCD_SetCursor
* 功能:设置显示位置
* 参数:x - 列位置 (0~7,每列16点阵宽度,共8列)
*       y - 行位置 (0~3,共4行)
* 说明:ST7920 12864 可显示 4行×8个汉字 或 4行×16个ASCII字符
**********************************************/
void LCD_SetCursor(unsigned char x, unsigned char y)
{
    unsigned char addr;

    switch(y)
    {
      case 0: addr = 0x80 + x; break;   // 第1行 DDRAM 地址 0x80~0x87
      case 1: addr = 0x90 + x; break;   // 第2行 DDRAM 地址 0x90~0x97
      case 2: addr = 0x88 + x; break;   // 第3行 DDRAM 地址 0x88~0x8F
      case 3: addr = 0x98 + x; break;   // 第4行 DDRAM 地址 0x98~0x9F
      default: addr = 0x80; break;
    }

    LCD_WriteCmd(addr);
}

/**********************************************
* 函数:LCD_ShowChar
* 功能:显示一个ASCII字符
* 参数:x - 列位置 (0~15)
*       y - 行位置 (0~3)
*       ch - 要显示的字符
**********************************************/
void LCD_ShowChar(unsigned char x, unsigned char y, unsigned char ch)
{
    LCD_SetCursor(x, y);      // 设置位置(ASCII字符每行16个,x范围0~15)
    LCD_WriteData(ch);          // 写入字符数据
}

/**********************************************
* 函数:LCD_ShowString
* 功能:显示字符串(ASCII字符)
* 参数:x - 起始列 (0~15)
*       y - 行 (0~3)
*       str - 字符串指针
**********************************************/
void LCD_ShowString(unsigned char x, unsigned char y, unsigned char *str)
{
    LCD_SetCursor(x, y);
    while(*str != '\0')
    {
      LCD_WriteData(*str++);
    }
}

/**********************************************
* 函数:LCD_ShowChinese
* 功能:显示汉字(ST7920内建中文字库GB2312)
* 参数:x - 列位置 (0~7,汉字占2个ASCII字符宽度)
*       y - 行位置 (0~3)
*       ch - 汉字编码高字节
*       cl - 汉字编码低字节
* 说明:ST7920字库中,汉字编码为GB2312内码,
*       高字节范围 0xA1~0xF7,低字节范围 0xA1~0xFE
**********************************************/
void LCD_ShowChinese(unsigned char x, unsigned char y,
                     unsigned char ch, unsigned char cl)
{
    LCD_SetCursor(x, y);
    LCD_WriteData(ch);          // 写入汉字编码高字节
    LCD_WriteData(cl);          // 写入汉字编码低字节
}

/**********************************************
* 函数:LCD_DisplayPicture
* 功能:显示全屏图片(扩展功能,需用户提供128×64点阵数据)
* 参数:*pic - 图片数据指针(1024字节)
* 说明:需先切换到绘图模式(0x34),使用GDRAM
**********************************************/
void LCD_DisplayPicture(unsigned char code *pic)
{
    unsigned char i, j;
    unsigned char row, col;

    // 切换到扩展指令集
    LCD_WriteCmd(0x34);         // 扩展指令集,关闭绘图显示
    delay_ms(2);

    // 写入GDRAM(128×64点阵 = 1024字节)
    for(i = 0; i &lt; 32; i++)   // 32行(垂直方向32字节)
    {
      for(j = 0; j &lt; 8; j++)// 8列(水平方向8个16位数据)
      {
            // 设置垂直地址 (0~31)
            if(i &lt; 16)
                LCD_WriteCmd(0x80 + i);      // 上半屏
            else
                LCD_WriteCmd(0x80 + i - 16); // 下半屏
         
            // 设置水平地址 (0~7)
            LCD_WriteCmd(0x80 + j);
         
            // 写入两个字节(高8位和低8位)
            LCD_WriteData(pic);
            LCD_WriteData(pic);
      }
    }

    // 打开绘图显示
    LCD_WriteCmd(0x36);         // 扩展指令集,打开绘图显示
    delay_ms(2);

    // 切回基本指令集
    LCD_WriteCmd(0x30);
    delay_ms(2);
}

/*==================== I/O口初始化 ====================*/
/**********************************************
* 函数:GPIO_Init
* 功能:初始化I/O口模式
* 说明:STC15H2K64S4的I/O口默认为准双向口,
*       控制引脚设为推挽输出以提高驱动能力
**********************************************/
void GPIO_Init(void)
{
    // P0口全部设为推挽输出(数据总线)
    P0M1 = 0x00;
    P0M0 = 0xFF;

    // P1.0~P1.4 设为推挽输出(控制引脚)
    // P1.0(RS), P1.1(RW), P1.2(EN), P1.3(CS), P1.4(RST)
    P1M1 = 0x00;                // P1.0~P1.4 推挽输出
    P1M0 = 0x1F;                // 低5位设为1
}

/*==================== 主函数 ====================*/
/**********************************************
* 函数:main
* 功能:主程序入口
**********************************************/
void main(void)
{
    unsigned char i;

    // 关闭全局中断(本程序未使用中断)
    EA = 0;

    // 初始化I/O口
    GPIO_Init();

    // 初始化12864液晶
    LCD_Init();

    // ---- 显示演示内容 ----

    // 第1行:显示标题
    LCD_ShowString(0, 0, &quot; STC15H2K64S4 &quot;);

    // 第2行:显示汉字(使用ST7920内建字库)
    // 注意:汉字编码为GB2312内码
    // &quot;欢&quot; = 0xB6 0xFE, &quot;迎&quot; = 0xD3 0xAD
    // &quot;使&quot; = 0xCA 0xB9, &quot;用&quot; = 0xD3 0xC3
    LCD_SetCursor(0, 1);
    LCD_WriteData(0xB6); LCD_WriteData(0xFE);// 欢
    LCD_WriteData(0xD3); LCD_WriteData(0xAD);// 迎
    LCD_WriteData(0xCA); LCD_WriteData(0xB9);// 使
    LCD_WriteData(0xD3); LCD_WriteData(0xC3);// 用
    LCD_WriteData(0x20); LCD_WriteData(0x20);// 空格
    LCD_WriteData(0x20); LCD_WriteData(0x20);// 空格
    LCD_WriteData(0x20); LCD_WriteData(0x20);// 空格
    LCD_WriteData(0x20); LCD_WriteData(0x20);// 空格

    // 第3行:显示数字和符号
    LCD_ShowString(0, 2, &quot;ABC 123 + - * &quot;);

    // 第4行:显示滚动信息
    LCD_ShowString(0, 3, &quot;STC 51 12864&quot;);

    // 延时2秒
    delay_ms(2000);

    // ---- 清屏后显示更多内容 ----
    LCD_Clear();

    // 第1行
    LCD_ShowString(0, 0, &quot;LCD12864 DEMO&quot;);

    // 第2行:显示自定义汉字组合
    // &quot;单&quot;=0xB5 0xA5, &quot;片&quot;=0xC6 0xAC, &quot;机&quot;=0xBB 0xFA
    // &quot;液&quot;=0xD2 0xBA, &quot;晶&quot;=0xBE 0xA7, &quot;屏&quot;=0xC1 0xBE
    LCD_SetCursor(0, 1);
    LCD_WriteData(0xB5); LCD_WriteData(0xA5);// 单
    LCD_WriteData(0xC6); LCD_WriteData(0xAC);// 片
    LCD_WriteData(0xBB); LCD_WriteData(0xFA);// 机
    LCD_WriteData(0xD2); LCD_WriteData(0xBA);// 液
    LCD_WriteData(0xBE); LCD_WriteData(0xA7);// 晶
    LCD_WriteData(0xC1); LCD_WriteData(0xBE);// 屏
    LCD_WriteData(0x20); LCD_WriteData(0x20);// 空格
    LCD_WriteData(0x20); LCD_WriteData(0x20);// 空格

    // 第3行
    LCD_ShowString(0, 2, &quot;8-Bit Parallel&quot;);

    // 第4行
    LCD_ShowString(0, 3, &quot;STC15H2K64S4   &quot;);

    // ---- 主循环:交替显示 ----
    while(1)
    {
      // 每3秒切换一次显示内容
      delay_ms(3000);
      
      LCD_Clear();
      LCD_ShowString(0, 0, &quot;Hello World!   &quot;);
      LCD_ShowString(0, 1, &quot;STCMCUDIY&quot;);
      LCD_ShowString(0, 2, &quot;12864Display &quot;);
      LCD_ShowString(0, 3, &quot;2025STCCo. &quot;);
      
      delay_ms(3000);
      
      LCD_Clear();
      LCD_ShowString(0, 0, &quot;Thank You!   &quot;);
      LCD_ShowString(0, 1, &quot;For Using      &quot;);
      LCD_ShowString(0, 2, &quot;STC15H2K64S4   &quot;);
      LCD_ShowString(0, 3, &quot;Have Fun!      &quot;);
    }
}
</code></pre>
<hr />
<h2>硬件接线说明</h2>
<table>
<thead>
<tr>
<th>12864 引脚</th>
<th>功能</th>
<th>接 STC15H2K64S4</th>
</tr>
</thead>
<tbody>
<tr>
<td>RS (CS)</td>
<td>数据/指令选择</td>
<td>P1.0</td>
</tr>
<tr>
<td>RW (SID)</td>
<td>读/写选择</td>
<td>P1.1</td>
</tr>
<tr>
<td>E (SCLK)</td>
<td>使能信号</td>
<td>P1.2</td>
</tr>
<tr>
<td>CS1/CS2</td>
<td>片选(可选)</td>
<td>P1.3(如无则悬空)</td>
</tr>
<tr>
<td>RST</td>
<td>复位(可选)</td>
<td>P1.4(如无则悬空)</td>
</tr>
<tr>
<td>PSB</td>
<td>并/串选择</td>
<td>VCC(接高电平)</td>
</tr>
<tr>
<td>DB0~DB7</td>
<td>8位数据总线</td>
<td>P0.0~P0.7</td>
</tr>
<tr>
<td>VCC</td>
<td>电源+5V</td>
<td>+5V</td>
</tr>
<tr>
<td>GND</td>
<td>电源地</td>
<td>GND</td>
</tr>
<tr>
<td>V0</td>
<td>对比度调节</td>
<td>接10K电位器中点</td>
</tr>
<tr>
<td>LED+</td>
<td>背光正极</td>
<td>串100Ω电阻接+5V</td>
</tr>
<tr>
<td>LED-</td>
<td>背光负极</td>
<td>GND</td>
</tr>
</tbody>
</table>
<blockquote>
<p><strong>注意</strong>:</p>
<ol>
<li>如果您的 12864 模块是 <strong>5V 供电</strong>,STC15H2K64S4 也是 5V 供电,可直接连接。</li>
<li>如果您的模块是 <strong>3.3V 供电</strong>,需注意电平匹配,建议加电平转换或使用 STC15H 的 3.3V 版本。</li>
<li>PSB 引脚必须接高电平(VCC),否则模块会工作在串行模式。</li>
<li>V0 引脚通过 10K 电位器分压调节对比度,通常调至 0~1V 左右。</li>
</ol>
</blockquote>
<hr />
<h2>使用说明</h2>
<h3>1. 编译环境</h3>
<ul>
<li>使用 <strong>Keil C51</strong> 编译,选择芯片型号为 <strong>STC15H2K64S4</strong>。</li>
<li>在 Keil 中需包含头文件 <code>STC15H2K64S4.h</code>(可从 STC-ISP 软件中获取)。</li>
</ul>
<h3>2. ISP 下载设置</h3>
<ul>
<li>使用 <strong>STC-ISP 下载软件</strong>,选择芯片型号 <strong>STC15H2K64S4</strong>。</li>
<li><strong>IRC 频率建议选择 11.0592MHz</strong>(与程序中的延时参数匹配)。</li>
<li>如果使用其他频率,请相应调整 <code>delay_us</code> 和 <code>delay_ms</code> 函数中的循环参数。</li>
</ul>
<h3>3. 程序功能</h3>
<ul>
<li>上电后显示欢迎信息,包含汉字和 ASCII 字符。</li>
<li>2 秒后切换显示第二屏内容。</li>
<li>之后进入主循环,每 3 秒交替显示两组信息。</li>
</ul>
<h3>4. 关于汉字显示</h3>
<ul>
<li>ST7920 控制器内置 <strong>GB2312 中文字库</strong>,可直接写入汉字内码显示。</li>
<li>汉字内码为 2 字节:高字节范围 <code>0xA1~0xF7</code>,低字节范围 <code>0xA1~0xFE</code>。</li>
<li>程序中已给出&quot;欢迎使用单片机液晶屏&quot;等汉字的编码示例。</li>
<li>如需显示其他汉字,可使用 STC-ISP 软件中的&quot;汉字内码查询&quot;工具获取编码。</li>
</ul>
<h3>5. 关于图片显示</h3>
<ul>
<li>程序提供了 <code>LCD_DisplayPicture()</code> 函数框架,支持显示 128×64 点阵图片。</li>
<li>图片数据需为 1024 字节(128×64/8),可使用取模软件生成。</li>
<li>使用时需将图片数据定义为 <code>code</code> 数组。</li>
</ul>
<hr />
<p>如果您需要 <strong>4 线 SPI 串行模式</strong> 的版本,或者需要调整引脚定义,请告诉我,我可以为您修改程序。</p>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC15H2K64S4%2012864%20%E6%B6%B2%E6%99%B6%E5%B1%8F%20%E9%A9%B1%E5%8A%A8%20%E7%A8%8B%E5%BA%8F&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC15H2K64S4 12864 液晶屏 驱动 程序&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=24648&amp;highlight=%E5%B8%AESTC15H2K64S4%20%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E7%94%A8%E9%80%94%EF%BC%9A%0A%E9%A9%B1%E5%8A%A812864%E6%B6%B2%E6%99%B6%E5%B1%8F&amp;page=1&amp;extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=23692&amp;highlight=%E5%B8%AESTC15H2K64S4%20%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E7%94%A8%E9%80%94%EF%BC%9A%0A%E9%A9%B1%E5%8A%A812864%E6%B6%B2%E6%99%B6%E5%B1%8F&amp;page=1&amp;extra=#pid216102">STC15W4K32S4移植到 STC15H2K64S4,ADC读出来不对</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=12003&amp;highlight=%E5%B8%AESTC15H2K64S4%20%E8%87%AA%E5%8A%A8%E5%86%99%E4%B8%80%E4%B8%AA%E5%AE%8C%E6%95%B4%E7%9A%84%E6%BC%94%E7%A4%BA%E7%A8%8B%E5%BA%8F%EF%BC%8C%E7%94%A8%E9%80%94%EF%BC%9A%0A%E9%A9%B1%E5%8A%A812864%E6%B6%B2%E6%99%B6%E5%B1%8F&amp;page=1&amp;extra=#pid122898">辛苦费 RMB500, 谁第一个用立创EDA复制 15H2K64S4实验箱</a></li>
</ol>
<p>⏱️ 查找用时 22.5s | 总回答用时 73.8s</p>

32G12K128 发表于 2026-7-3 10:17:21

STC8H2K12U如何读写EEPROM,麻烦给个范例



32G12K128 发表于 2026-7-3 10:17:42

<p>好的,信息已经足够。下面为您整理 STC8H2K12U 的 EEPROM 读写方法及完整范例。</p>
<hr />
<h2>STC8H2K12U EEPROM 读写说明</h2>
<h3>1. 关于 STC8H2K12U 的 EEPROM 大小</h3>
<p>根据手册&quot;EEPROM大小及地址&quot;章节,<strong>STC8H2K12U 的 EEPROM 大小为&quot;用户自定义&quot;</strong>,即在 ISP 下载时,可以在整个 Flash 空间中自行规划任意大小的 EEPROM 空间。EEPROM 总是从后向前规划。</p>
<p>例如:STC8H2K12U 的 Flash 为 12K,若在 ISP 下载时设置 4K 为 EEPROM,则:</p>
<ul>
<li>IAP 方式访问地址:<code>0000H ~ 0FFFH</code></li>
<li>MOVC 方式读取地址:<code>2000H ~ 2FFFH</code>(需加上程序空间偏移)</li>
</ul>
<blockquote>
<p><strong>建议</strong>:在 ISP 下载软件(STC-ISP)的&quot;硬件选项&quot;中设置好用户 EEPROM 大小,并确保读写地址在设置的范围内。</p>
</blockquote>
<h3>2. 关键寄存器说明</h3>
<table>
<thead>
<tr>
<th align="center">寄存器</th>
<th align="center">地址</th>
<th align="left">功能</th>
</tr>
</thead>
<tbody>
<tr>
<td align="center"><strong>IAP_DATA</strong></td>
<td align="center">C2H</td>
<td align="left">读/写数据寄存器</td>
</tr>
<tr>
<td align="center"><strong>IAP_ADDRH</strong></td>
<td align="center">C3H</td>
<td align="left">地址高字节</td>
</tr>
<tr>
<td align="center"><strong>IAP_ADDRL</strong></td>
<td align="center">C4H</td>
<td align="left">地址低字节</td>
</tr>
<tr>
<td align="center"><strong>IAP_CMD</strong></td>
<td align="center">C5H</td>
<td align="left">命令寄存器(01=读,10=写,11=扇区擦除)</td>
</tr>
<tr>
<td align="center"><strong>IAP_TRIG</strong></td>
<td align="center">C6H</td>
<td align="left">触发寄存器(先写5AH,再写A5H)</td>
</tr>
<tr>
<td align="center"><strong>IAP_CONTR</strong></td>
<td align="center">C7H</td>
<td align="left">控制寄存器(bit7 IAPEN 使能位)</td>
</tr>
<tr>
<td align="center"><strong>IAP_TPS</strong></td>
<td align="center">F5H</td>
<td align="left">等待时间控制(= 系统频率MHz,四舍五入取整)</td>
</tr>
</tbody>
</table>
<h3>3. 完整 C 语言范例代码</h3>
<pre><code class="language-c">/*---------------------------------------------------------------------*/
/* --- STC8H2K12U EEPROM 读写范例                                    */
/* --- 适用芯片: STC8H2K12U 及 STC8H 系列                              */
/* --- 下载时请在 ISP 软件中设置好用户 EEPROM 大小                      */
/*---------------------------------------------------------------------*/
#include &quot;stc8h.h&quot;
#include &quot;intrins.h&quot;

#define MAIN_Fosc       11059200L   // 主时钟频率
#define IAP_TPS_SETTING 11          // IAP_TPS = 频率(MHz)四舍五入

// 关闭 IAP 功能
void IapIdle(void)
{
    IAP_CONTR = 0;          // 关闭 IAP 功能
    IAP_CMD   = 0;          // 清除命令寄存器
    IAP_TRIG= 0;          // 清除触发寄存器
    IAP_ADDRH = 0x80;       // 将地址设置到非 IAP 区域
    IAP_ADDRL = 0;
}

// 从 EEPROM 读取一字节
unsigned char IapReadByte(unsigned int addr)
{
    unsigned char dat;

    IAP_CONTR = 0x80;               // 使能 IAP
    IAP_TPS   = IAP_TPS_SETTING;    // 设置等待参数
    IAP_CMD   = 1;                  // 读命令
    IAP_ADDRL = addr;               // 设置低地址
    IAP_ADDRH = addr &gt;&gt; 8;          // 设置高地址

    IAP_TRIG = 0x5A;                // 写触发命令 (必须先写 5AH)
    IAP_TRIG = 0xA5;                // 再写 A5H
    _nop_();                        // 等待操作完成

    dat = IAP_DATA;               // 读取数据
    IapIdle();                      // 关闭 IAP

    return dat;
}

// 写一字节到 EEPROM(注意:只能将 1 写为 0,若需写 1 必须先擦除扇区)
void IapProgramByte(unsigned int addr, unsigned char dat)
{
    IAP_CONTR = 0x80;               // 使能 IAP
    IAP_TPS   = IAP_TPS_SETTING;    // 设置等待参数
    IAP_CMD   = 2;                  // 写命令
    IAP_ADDRL = addr;               // 设置低地址
    IAP_ADDRH = addr &gt;&gt; 8;          // 设置高地址
    IAP_DATA= dat;                // 写入待写数据

    IAP_TRIG = 0x5A;                // 写触发命令 (必须先写 5AH)
    IAP_TRIG = 0xA5;                // 再写 A5H
    _nop_();                        // 等待操作完成

    IapIdle();                      // 关闭 IAP
}

// 扇区擦除(每扇区 512 字节,地址低 9 位无效)
void IapEraseSector(unsigned int addr)
{
    IAP_CONTR = 0x80;               // 使能 IAP
    IAP_TPS   = IAP_TPS_SETTING;    // 设置等待参数
    IAP_CMD   = 3;                  // 扇区擦除命令
    IAP_ADDRL = addr;               // 设置低地址
    IAP_ADDRH = addr &gt;&gt; 8;          // 设置高地址

    IAP_TRIG = 0x5A;                // 写触发命令 (必须先写 5AH)
    IAP_TRIG = 0xA5;                // 再写 A5H
    _nop_();                        // 等待操作完成

    IapIdle();                      // 关闭 IAP
}

// 连续写入 n 字节(自动处理跨扇区擦除)
void IapWriteNByte(unsigned int addr, unsigned char *pdat, unsigned int len)
{
    unsigned int i;

    // 先擦除目标地址所在的扇区
    // 注意:如果扇区中有其他需要保留的数据,需先读到 RAM 暂存
    IapEraseSector(addr);

    for (i = 0; i &lt; len; i++)
    {
      IapProgramByte(addr + i, pdat);
    }
}

// 连续读取 n 字节
void IapReadNByte(unsigned int addr, unsigned char *pdat, unsigned int len)
{
    unsigned int i;

    for (i = 0; i &lt; len; i++)
    {
      pdat = IapReadByte(addr + i);
    }
}

// 使用 MOVC 方式读取 EEPROM(只读,更快)
// 注意:MOVC 方式地址 = EEPROM物理地址 + 程序空间大小偏移
// 例如 STC8H2K12U 若程序空间 12K,则偏移为 0x3000
#define EEPROM_MOVC_OFFSET0x3000// 请根据实际程序空间修改

unsigned char IapReadByMOVC(unsigned int addr)
{
    unsigned char code *p;
    p = (unsigned char code *)(addr + EEPROM_MOVC_OFFSET);
    return *p;
}

/*------------------------ 主函数测试示例 ----------------------------*/
void main(void)
{
    unsigned char test_write = {0x12, 0x34, 0x56, 0x78};
    unsigned char test_read= {0};
    unsigned inteeprom_addr= 0x0000;// EEPROM 起始地址

    // 1. 写入数据(先擦除再写)
    IapWriteNByte(eeprom_addr, test_write, 4);

    // 2. 读取数据
    IapReadNByte(eeprom_addr, test_read, 4);

    // 此时 test_read~test_read 应等于 0x12,0x34,0x56,0x78

    while (1);
}
</code></pre>
<h3>4. 重要注意事项</h3>
<ol>
<li><strong>写操作特性</strong>:EEPROM 写操作只能将字节中的 <strong>1 写为 0</strong>,若需将 0 恢复为 1,<strong>必须先执行扇区擦除</strong>(擦除后扇区全部变为 FFH)。</li>
<li><strong>扇区擦除单位</strong>:每次擦除 512 字节(一个扇区),地址的低 9 位无效。若扇区中有需要保留的数据,需先读到 RAM 暂存,擦除后再写回。</li>
<li><strong>IAP_TPS 设置</strong>:必须正确设置,<code>IAP_TPS = 系统工作频率(MHz)</code>,四舍五入取整。例如 11.0592MHz 设为 11,24MHz 设为 24。</li>
<li><strong>触发序列的原子性</strong>:手册强调,如果在主循环和中断中都有 IAP 操作,则触发前必须<strong>关闭总中断(EA=0)</strong>,触发完成后再开启,防止触发序列被中断打断导致错误。</li>
<li><strong>STC8H2K12U 的特殊情况</strong>:据论坛用户反馈(帖子 TID=19001),STC8H2K12U 在连续写入后连续读出时,读出的数据可能存在<strong>偏移</strong>(第 0 字节错误,数据跑到第 1 字节)。建议在 <code>IAP_TRIG</code> 触发后适当增加延时或 <code>_nop_()</code> 数量,或使用 AiCube-ISP 一键生成的代码。</li>
<li><strong>EEPROM 大小设置</strong>:STC8H2K12U 的 EEPROM 为<strong>用户自定义</strong>,下载时务必在 ISP 软件的&quot;硬件选项&quot;中设置好 EEPROM 大小,否则无法正常使用。</li>
</ol>
<h3>5. 更简单的读取方式(MOVC)</h3>
<p>如果只需要<strong>读取</strong> EEPROM(不需要写入/擦除),可以使用 MOVC 指令方式,更加快捷:</p>
<pre><code class="language-c">// addr 为 EEPROM 内的偏移地址(从 0 开始)
// IAP_OFFSET 为 EEPROM 在程序空间中的起始地址偏移
// 例如:STC8H2K12U 程序 12K,EEPROM 从后规划 4K
// 则 IAP_OFFSET = 0x3000(12K - 4K = 8K = 0x2000,需根据实际设置计算)

unsigned char EEPROM_Read_MOVC(unsigned int addr)
{
    return *(unsigned char code *)(addr + IAP_OFFSET);
}
</code></pre>
<blockquote>
<p><strong>信息来源</strong>:STC8H 数据手册&quot;20 IAP/EEPROM/DATA-FLASH&quot;章节,以及 STC 官方论坛帖子 TID=19001、TID=4413。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC8H2K12U%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99%20EEPROM%20%E9%BA%BB%E7%83%A6%E7%BB%99%E4%B8%AA%E8%8C%83%E4%BE%8B&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC8H2K12U如何读写 EEPROM 麻烦给个范例&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=20473&amp;highlight=STC8H2K12U%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99EEPROM%2C%E9%BA%BB%E7%83%A6%E7%BB%99%E4%B8%AA%E8%8C%83%E4%BE%8B&amp;page=1&amp;extra=#pid189541">STC8H2K12U如何将 7 / 8引脚,配置为P1.0 P1.1</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=18985&amp;highlight=STC8H2K12U%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99EEPROM%2C%E9%BA%BB%E7%83%A6%E7%BB%99%E4%B8%AA%E8%8C%83%E4%BE%8B&amp;page=2&amp;extra=#pid191011">写 EEPROM遇到的问题</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=20611&amp;highlight=STC8H2K12U%E5%A6%82%E4%BD%95%E8%AF%BB%E5%86%99EEPROM%2C%E9%BA%BB%E7%83%A6%E7%BB%99%E4%B8%AA%E8%8C%83%E4%BE%8B&amp;page=1&amp;extra=#pid190806">AI8H2K12U-45I-SOP8,STC8H2K12</a></li>
</ol>
<p>⏱️ 查找用时 16.8s | 总回答用时 64.5s</p>

32位8051 发表于 2026-7-3 10:25:00

STC8H8K64U串口连续发送60个字节的数据,帮我生成程序


32位8051 发表于 2026-7-3 10:25:07

<p>好的,信息已经足够。下面我为您生成一个完整的 STC8H8K64U 串口1连续发送60字节数据的程序。</p>
<hr />
<h2>STC8H8K64U 串口1连续发送60字节数据程序</h2>
<p>以下程序使用<strong>串口1</strong>(默认引脚 P3.0/RxD, P3.1/TxD),采用<strong>定时器2作为波特率发生器</strong>,工作频率 <strong>11.0592MHz</strong>,波特率 <strong>115200</strong>。提供<strong>查询方式</strong>和<strong>中断方式</strong>两个版本。</p>
<hr />
<h3>版本一:查询方式(简单直接)</h3>
<pre><code class="language-c">#include &quot;stc8h.h&quot;
#include &quot;intrins.h&quot;

#define FOSC      11059200UL      // 系统工作频率 11.0592MHz
#define BAUD      115200UL      // 目标波特率

// 待发送的60字节数据
unsigned char code tx_buffer = {
    0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
    0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,
    0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,
    0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
    0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,
    0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B
};

// 串口1发送单字节(查询方式)
void UART1_SendByte(unsigned char dat)
{
    SBUF = dat;                     // 写入发送缓冲
    while (!TI);                  // 等待发送完成
    TI = 0;                         // 清除发送完成标志
}

// 串口1发送多字节(查询方式)
void UART1_SendBuffer(unsigned char *p, unsigned char len)
{
    unsigned char i;
    for (i = 0; i &lt; len; i++)
    {
      UART1_SendByte(p);
    }
}

// 串口1初始化(定时器2做波特率发生器,115200bps@11.0592MHz)
void UART1_Init(void)
{
    P_SW2 |= 0x80;                  // 使能访问XFR(扩展RAM区特殊功能寄存器)

    // 设置所有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;

    SCON = 0x50;                  // 模式1(8位UART),REN=1使能接收
    // 定时器2重装值:65536 - FOSC/4/BAUD = 65536 - 11059200/4/115200 = 0FFE8H
    T2L = 0xE8;                     // 低字节
    T2H = 0xFF;                     // 高字节
    AUXR = 0x15;                  // T2R=1启动定时器2,T2x12=1(1T模式),S1ST2=1(串口1选择定时器2)
}

void main(void)
{
    UART1_Init();                   // 初始化串口1

    while (1)
    {
      UART1_SendBuffer((unsigned char *)tx_buffer, 60);   // 发送60字节
      // 可根据需要添加延时,控制发送间隔
    }
}
</code></pre>
<hr />
<h3>版本二:中断方式(更高效,不阻塞CPU)</h3>
<pre><code class="language-c">#include &quot;stc8h.h&quot;
#include &quot;intrins.h&quot;

#define FOSC      11059200UL      // 系统工作频率 11.0592MHz
#define BAUD      115200UL      // 目标波特率

// 待发送的60字节数据
unsigned char code tx_buffer = {
    0x00,0x01,0x02,0x03,0x04,0x05,0x06,0x07,0x08,0x09,
    0x0A,0x0B,0x0C,0x0D,0x0E,0x0F,0x10,0x11,0x12,0x13,
    0x14,0x15,0x16,0x17,0x18,0x19,0x1A,0x1B,0x1C,0x1D,
    0x1E,0x1F,0x20,0x21,0x22,0x23,0x24,0x25,0x26,0x27,
    0x28,0x29,0x2A,0x2B,0x2C,0x2D,0x2E,0x2F,0x30,0x31,
    0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x39,0x3A,0x3B
};

volatile unsigned char tx_index;    // 当前发送位置索引
bit sending;                        // 发送进行中标志

// 串口1中断服务函数(中断向量0x0023)
void UART1_Isr(void) interrupt 4
{
    if (TI)                         // 发送完成中断
    {
      TI = 0;                     // 清除发送中断标志

      if (tx_index &lt; 60)          // 还有数据待发送
      {
            SBUF = tx_buffer;   // 发送下一个字节
            tx_index++;
      }
      else                        // 全部发送完毕
      {
            sending = 0;            // 清除发送标志
      }
    }

    if (RI)                         // 接收中断(本例未使用)
    {
      RI = 0;                     // 清除接收中断标志
      // 可在此处添加接收处理代码
    }
}

// 启动发送60字节数据(非阻塞,由中断逐字节发送)
void UART1_Send60Bytes(void)
{
    if (sending) return;            // 如果正在发送则忽略本次请求

    sending = 1;                  // 置位发送标志
    tx_index = 0;                   // 从第0个字节开始
    SBUF = tx_buffer;            // 触发第一个字节发送
    tx_index++;                     // 指向下一个字节
}

// 串口1初始化(定时器2做波特率发生器,115200bps@11.0592MHz)
void UART1_Init(void)
{
    P_SW2 |= 0x80;                  // 使能访问XFR

    // 设置所有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;

    SCON = 0x50;                  // 模式1(8位UART),REN=1使能接收
    // 定时器2重装值:65536 - FOSC/4/BAUD = 0FFE8H
    T2L = 0xE8;
    T2H = 0xFF;
    AUXR = 0x15;                  // T2R=1, T2x12=1, S1ST2=1

    sending = 0;                  // 初始化发送标志
    tx_index = 0;

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

void main(void)
{
    UART1_Init();                   // 初始化串口1

    while (1)
    {
      UART1_Send60Bytes();      // 启动发送60字节
      // 此时CPU可以处理其他任务,发送由中断后台完成
      // 可根据需要添加延时,控制发送间隔
    }
}
</code></pre>
<hr />
<h3>关键说明</h3>
<table>
<thead>
<tr>
<th>项目</th>
<th>说明</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>串口</strong></td>
<td>串口1,默认引脚 P3.0(RxD) / P3.1(TxD)</td>
</tr>
<tr>
<td><strong>波特率</strong></td>
<td>115200 bps(11.0592MHz晶振下精确无误差)</td>
</tr>
<tr>
<td><strong>定时器</strong></td>
<td>定时器2,1T模式,重装值 0xFFE8</td>
</tr>
<tr>
<td><strong>数据格式</strong></td>
<td>8位数据,1位停止位,无校验</td>
</tr>
<tr>
<td><strong>寄存器配置</strong></td>
<td>依据手册&quot;串口通信&quot;章节,SCON=0x50(模式1+REN),AUXR=0x15(启动T2+1T+S1ST2)</td>
</tr>
</tbody>
</table>
<p><strong>波特率计算</strong>(手册&quot;串口通信&quot;章节公式):</p>
<ul>
<li>定时器2(1T模式)波特率 = <span class="language-math">​F_{OSC} / 4 / (65536 - \text{重装值})</span></li>
<li>重装值 = $65536 - F_{OSC} / 4 / \text{BAUD} = 65536 - 11059200 / 4 / 115200 = 0\text{FFE8H}$</li>
</ul>
<p><strong>如需更改波特率或晶振频率</strong>,可参考手册&quot;串口通信&quot;章节中的波特率表格,修改 T2L/T2H 重装值即可。</p>
<blockquote>
<p>注:以上代码基于 STC8H 手册&quot;串口通信&quot;章节及&quot;中断系统&quot;章节中的寄存器定义和示例编写。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&amp;mod=forum&amp;q=STC8H8K64U%20%E4%B8%B2%E5%8F%A3%20%E5%8F%91%E9%80%81%2060%E5%AD%97%E8%8A%82&amp;f=_all&amp;m=yes&amp;syn=yes&amp;s=relevance&amp;fid=0">🔍 搜索 &quot;STC8H8K64U 串口 发送 60字节&quot;</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=10589&amp;highlight=STC8H8K64U%E4%B8%B2%E5%8F%A3%E8%A6%81%E8%BF%9E%E7%BB%AD%E5%8F%91%E9%80%8160%E4%B8%AA%E5%AD%97%E8%8A%82%E7%9A%84%E6%95%B0%E6%8D%AE%2C%E5%B8%AE%E6%88%91%E7%94%9F%E6%88%90%E7%A8%8B%E5%BA%8F&amp;page=1&amp;extra=#pid104712">请问8H8K64U的SPI连续发送数据的问题</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&amp;tid=9654&amp;highlight=STC8H8K64U%E4%B8%B2%E5%8F%A3%E8%A6%81%E8%BF%9E%E7%BB%AD%E5%8F%91%E9%80%8160%E4%B8%AA%E5%AD%97%E8%8A%82%E7%9A%84%E6%95%B0%E6%8D%AE%2C%E5%B8%AE%E6%88%91%E7%94%9F%E6%88%90%E7%A8%8B%E5%BA%8F&amp;page=1&amp;extra=#pid93836">STC8H8K64U是否支持 HID包发送实际长度数据?而不用固定64字节</a></li>
<li>一起学 AiCube之 AI8H8K64U应用, printf_usb(​&quot;Hello World !\r\n​&quot;);</li>
</ol>
<p>⏱️ 查找用时 13.4s | 总回答用时 32.6s</p>

32位8051-AI 发表于 2026-7-3 11:18:09


STC8A8K64D4全球唯一ID(地址:0FDF9开始7个字节)
与EEPROM相同地址(地址:0FDF9)是否冲突?
做EEPROM就不能读全球唯一ID了吗?

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