<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 >> 8;</code><br />
<code>IAP_ADDRL = addr & 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 "stc8h.h"
#include "intrins.h"
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 >> 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 >> 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 >> 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&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC8H1K08的 EEPROM地址是多少?如何读写?"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=16674&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&page=1&extra=#pid156211">使用stc8h1k24这个芯片操作eeprom地址多少</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=8592&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&page=1&extra=#pid81737">STC8H1K08 程序/EEPROM, 空间大小,地址 问题 | KEIL C51试用版</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=20241&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&page=1&extra=#pid187442">关于stc8h1k08的eeprom的IAP访问和地址访问</a></li>
</ol>
<p>⏱️ 查找用时 6.8s | 总回答用时 25.2s</p>
帮 STC32G12K128自动写一个完整的演示程序,用于做汽车行车记录仪
<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 "stc32g.h"
#include "intrins.h"
#include "string.h"
// 系统时钟频率定义(使用内部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 << 0) // 定时采样事件
#define EVT_USB_RX (1 << 1) // USB接收事件
#define EVT_ALARM (1 << 2) // 报警事件
#define EVT_SAVE_LOG (1 << 3) // 保存日志事件
#endif
</code></pre>
<h3>2. eeprom.h - EEPROM操作头文件</h3>
<pre><code class="language-c">#ifndef __EEPROM_H__
#define __EEPROM_H__
#include "config.h"
// 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 "config.h"
#include "eeprom.h"
// USB库头文件(需从STC-ISP软件中获取)
#include "stc32_stc8_usb.h"
// ==================== 全局变量 ====================
// 事件标志
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 << 2) | (1 << 3) | (1 << 4);
P3M1 &= ~((1 << 2) | (1 << 3) | (1 << 4));
// 按键输入 - 高阻输入
// P3.5 - 模式按键
P3M0 &= ~(1 << 5);
P3M1 |= (1 << 5);
// ADC输入通道 - 高阻输入
// P1.0~P1.3
P1M0 &= ~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 << 7); // T0x12 = 1, 1T模式
TMOD &= 0xF0; // 清除T0模式位
TMOD |= 0x00; // 模式0:16位自动重装载
// 设置重载值(1ms定时)
// 实际值需要根据主频精确计算
// 这里使用33.1776MHz计算
u16 reload = 65536 - (u16)(MAIN_Fosc / 1000);
TL0 = reload & 0xFF;
TH0 = (reload >> 8) & 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下载时选择"USB CDC"模式
// 并使能"使用内置USB"
// 设置USB时钟源为内部48MHz IRC
IRC48MCR = 0x80; // 启动内部48M IRC
while (!(IRC48MCR & 1)); // 等待时钟稳定
IRCBAND |= 0x80; // 选择IRC48M作为USB时钟源
IRCBAND &= ~0x40;
// 调用USB库初始化函数
usb_init();
// 设置不停电下载命令
set_usb_ispcmd("@STCISP#");
}
// ==================== 延时函数 ====================
void Delay_ms(u16 ms)
{
u16 i, j;
for (i = 0; i < ms; i++)
{
// 粗略延时约1ms(主频33.1776MHz)
for (j = 0; j < (MAIN_Fosc / 13000); j++);
}
}
// ==================== ADC读取函数 ====================
u16 ADC_ReadChannel(u8 ch)
{
u16 result;
// 选择ADC通道
ADC_CONTR = (ADC_CONTR & 0xF0) | ch;
// 启动ADC转换
ADC_START = 1;
// 等待转换完成
_nop_(); _nop_();
while (!ADC_FLAG);
// 清除标志
ADC_FLAG = 0;
// 读取结果(右对齐模式)
result = ((u16)ADC_RES << 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 << 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 = &g_LogBuffer;
pEntry->voltage = g_CurrentSample.voltage;
pEntry->accel_x = g_CurrentSample.accel_x;
pEntry->accel_y = g_CurrentSample.accel_y;
pEntry->accel_z = g_CurrentSample.accel_z;
pEntry->temperature = g_CurrentSample.temperature;
pEntry->timestamp = g_CurrentSample.timestamp;
g_LogIndex++;
// 如果缓冲区满了,保存到EEPROM
if (g_LogIndex >= LOG_ENTRIES_PER_SECTOR)
{
g_EventFlags |= EVT_SAVE_LOG;
}
// 检查是否需要报警(加速度异常)
CheckAlarm();
}
void CheckAlarm(void)
{
// 模拟加速度异常检测
// 如果加速度值超出正常范围(假设正常范围1000~3000mV对应值)
if ((g_CurrentSample.accel_x > 3500) || (g_CurrentSample.accel_x < 500) ||
(g_CurrentSample.accel_y > 3500) || (g_CurrentSample.accel_y < 500) ||
(g_CurrentSample.accel_z > 3500) || (g_CurrentSample.accel_z < 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 & 0xFF);
IAP_ADDRH = (u8)((addr >> 8) & 0xFF);
IAP_ADDRE = (u8)((addr >> 16) & 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 & 0xFF);
IAP_ADDRH = (u8)((addr >> 8) & 0xFF);
IAP_ADDRE = (u8)((addr >> 16) & 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 & 0xFF);
IAP_ADDRH = (u8)((addr >> 8) & 0xFF);
IAP_ADDRE = (u8)((addr >> 16) & 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 < len; i++)
{
pBuf = IapReadByte(addr + i);
}
}
void EEPROM_WriteN(u32 addr, u8 *pBuf, u16 len)
{
u16 i;
for (i = 0; i < 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 >= (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,
"TIME:%lu,V:%u,AX:%u,AY:%u,AZ:%u,TEMP:%u\r\n",
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, "EEPROM: V=%u,AX=%u,AY=%u,AZ=%u\r\n",
((LogEntry_t *)buf)->voltage,
((LogEntry_t *)buf)->accel_x,
((LogEntry_t *)buf)->accel_y,
((LogEntry_t *)buf)->accel_z);
USB_SendData(out_buf, (int)strlen(out_buf));
}
else if (cmd == 'E' || cmd == 'e')
{
// 擦除所有EEPROM
u16 i;
for (i = 0; i < 128; i++)
{
EEPROM_EraseSector((u32)i * LOG_SECTOR_SIZE);
}
USB_SendData("EEPROM Erased!\r\n", 17);
}
else if (cmd == 'S' || cmd == 's')
{
// 发送当前状态
u8 buf;
sprintf(buf, "Mode:%d,Sample:%u,Tick:%u,Addr:0x%04X\r\n",
g_WorkMode, g_SampleCount, g_SysTick, g_EepromWriteAddr);
USB_SendData(buf, (int)strlen(buf));
}
else if (cmd == 'H' || cmd == 'h')
{
// 帮助信息
USB_SendData("Commands:\r\n", 12);
USB_SendData("R - Read EEPROM\r\n", 18);
USB_SendData("E - Erase EEPROM\r\n", 19);
USB_SendData("S - Show Status\r\n", 18);
USB_SendData("H - Help\r\n", 11);
}
// 处理完成,通知USB库接收下一包数据
usb_OUT_done();
}
// ==================== LED更新 ====================
void UpdateLEDs(void)
{
// 运行指示灯 - 系统节拍闪烁(1Hz)
static u8 tick_counter = 0;
tick_counter++;
if (tick_counter >= 500)// 500ms翻转
{
tick_counter = 0;
LED_RUN = !LED_RUN;
}
// 报警指示灯 - 由报警事件控制
if (g_EventFlags & 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("STC32G12K128 Car DVR Demo\r\n", 28);
USB_SendData("System Ready!\r\n", 15);
// 主循环
while (1)
{
// 处理USB接收数据(查询方式)
if (bUsbOutReady)
{
g_pUsbRxBuffer = (char *)UsbOutBuffer;
g_UsbRxLen = OutNumber;
g_EventFlags |= EVT_USB_RX;
}
// 处理事件
if (g_EventFlags & EVT_TIMER_SAMPLE)
{
g_EventFlags &= ~EVT_TIMER_SAMPLE;
// 读取所有ADC通道
ADC_ReadAllChannels();
// 处理采样数据
ProcessSampleData();
// 通过USB发送实时数据
SendLogToUSB();
g_SampleCount++;
}
if (g_EventFlags & EVT_USB_RX)
{
g_EventFlags &= ~EVT_USB_RX;
// 处理USB命令
ProcessUSBCommand();
}
if (g_EventFlags & EVT_SAVE_LOG)
{
g_EventFlags &= ~EVT_SAVE_LOG;
// 保存日志到EEPROM
SaveLogToEEPROM();
}
if (g_EventFlags & EVT_ALARM)
{
// 报警处理(持续点亮报警灯,发送报警信息)
static u8 alarm_sent = 0;
if (!alarm_sent)
{
USB_SendData("ALARM! Abnormal acceleration detected!\r\n", 42);
alarm_sent = 1;
}
// 延时后清除报警(模拟)
// 实际应用中需要按键确认或条件解除
if (g_SysTick % 5000 == 0)// 5秒后自动清除
{
g_EventFlags &= ~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 > 2) g_WorkMode = 0;
u8 buf;
sprintf(buf, "Mode changed to: %d\r\n", 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>使能"使用内置USB"</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&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC32G12K128 行车记录仪 演示程序 示例代码"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=24057&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&page=65&extra=#pid222111">【AI助手】@AiCube-ISP-V6.96Y中,【自动生成程序】+【查错高手】</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=24648&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&page=1&extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=7013&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&page=1&extra=#pid61838">STC32G12K128车规32位8051的应用场景</a></li>
</ol>
<p>⏱️ 查找用时 15.1s | 总回答用时 88.2s</p>
帮STC15H2K64S4 自动写一个完整的演示程序,用途:
驱动12864液晶屏
<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(数据/指令)->P1.0
* LCD_RW(读/写) ->P1.1
* LCD_EN(使能) ->P1.2
* LCD_CS(片选) ->P1.3(部分12864模块需要,如无片选可悬空)
* LCD_D0~D7 (数据总线) ->P0.0~P0.7
* LCD_RST (复位) ->P1.4(可选,部分模块需要)
* LCD_PSB (并/串选择)->接VCC (高电平=并行模式)
* 背光LED ->串接电阻接VCC/GND
******************************************************************************/
#include "STC15H2K64S4.h"
#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 < us; i++)
{
_nop_(); _nop_(); _nop_(); _nop_();
}
}
/**********************************************
* 函数:delay_ms
* 功能:毫秒级延时
* 参数:ms - 延时毫秒数
**********************************************/
void delay_ms(unsigned int ms)
{
unsigned int i, j;
for(i = 0; i < ms; i++)
for(j = 0; j < 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 & 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 < 32; i++) // 32行(垂直方向32字节)
{
for(j = 0; j < 8; j++)// 8列(水平方向8个16位数据)
{
// 设置垂直地址 (0~31)
if(i < 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, " STC15H2K64S4 ");
// 第2行:显示汉字(使用ST7920内建字库)
// 注意:汉字编码为GB2312内码
// "欢" = 0xB6 0xFE, "迎" = 0xD3 0xAD
// "使" = 0xCA 0xB9, "用" = 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, "ABC 123 + - * ");
// 第4行:显示滚动信息
LCD_ShowString(0, 3, "STC 51 12864");
// 延时2秒
delay_ms(2000);
// ---- 清屏后显示更多内容 ----
LCD_Clear();
// 第1行
LCD_ShowString(0, 0, "LCD12864 DEMO");
// 第2行:显示自定义汉字组合
// "单"=0xB5 0xA5, "片"=0xC6 0xAC, "机"=0xBB 0xFA
// "液"=0xD2 0xBA, "晶"=0xBE 0xA7, "屏"=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, "8-Bit Parallel");
// 第4行
LCD_ShowString(0, 3, "STC15H2K64S4 ");
// ---- 主循环:交替显示 ----
while(1)
{
// 每3秒切换一次显示内容
delay_ms(3000);
LCD_Clear();
LCD_ShowString(0, 0, "Hello World! ");
LCD_ShowString(0, 1, "STCMCUDIY");
LCD_ShowString(0, 2, "12864Display ");
LCD_ShowString(0, 3, "2025STCCo. ");
delay_ms(3000);
LCD_Clear();
LCD_ShowString(0, 0, "Thank You! ");
LCD_ShowString(0, 1, "For Using ");
LCD_ShowString(0, 2, "STC15H2K64S4 ");
LCD_ShowString(0, 3, "Have Fun! ");
}
}
</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>程序中已给出"欢迎使用单片机液晶屏"等汉字的编码示例。</li>
<li>如需显示其他汉字,可使用 STC-ISP 软件中的"汉字内码查询"工具获取编码。</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&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC15H2K64S4 12864 液晶屏 驱动 程序"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=24648&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&page=1&extra=#pid222079">STC【AI 助手】帮 STC8H8K64U自动写一个完整的演示程序,要包含下面这些:</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=23692&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&page=1&extra=#pid216102">STC15W4K32S4移植到 STC15H2K64S4,ADC读出来不对</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=12003&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&page=1&extra=#pid122898">辛苦费 RMB500, 谁第一个用立创EDA复制 15H2K64S4实验箱</a></li>
</ol>
<p>⏱️ 查找用时 22.5s | 总回答用时 73.8s</p>
STC8H2K12U如何读写EEPROM,麻烦给个范例
<p>好的,信息已经足够。下面为您整理 STC8H2K12U 的 EEPROM 读写方法及完整范例。</p>
<hr />
<h2>STC8H2K12U EEPROM 读写说明</h2>
<h3>1. 关于 STC8H2K12U 的 EEPROM 大小</h3>
<p>根据手册"EEPROM大小及地址"章节,<strong>STC8H2K12U 的 EEPROM 大小为"用户自定义"</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)的"硬件选项"中设置好用户 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 "stc8h.h"
#include "intrins.h"
#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 >> 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 >> 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 >> 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 < 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 < 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 软件的"硬件选项"中设置好 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 数据手册"20 IAP/EEPROM/DATA-FLASH"章节,以及 STC 官方论坛帖子 TID=19001、TID=4413。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&mod=forum&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&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC8H2K12U如何读写 EEPROM 麻烦给个范例"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=20473&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&page=1&extra=#pid189541">STC8H2K12U如何将 7 / 8引脚,配置为P1.0 P1.1</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=18985&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&page=2&extra=#pid191011">写 EEPROM遇到的问题</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=20611&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&page=1&extra=#pid190806">AI8H2K12U-45I-SOP8,STC8H2K12</a></li>
</ol>
<p>⏱️ 查找用时 16.8s | 总回答用时 64.5s</p>
STC8H8K64U串口连续发送60个字节的数据,帮我生成程序
<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 "stc8h.h"
#include "intrins.h"
#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 < 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 "stc8h.h"
#include "intrins.h"
#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 < 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>依据手册"串口通信"章节,SCON=0x50(模式1+REN),AUXR=0x15(启动T2+1T+S1ST2)</td>
</tr>
</tbody>
</table>
<p><strong>波特率计算</strong>(手册"串口通信"章节公式):</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>,可参考手册"串口通信"章节中的波特率表格,修改 T2L/T2H 重装值即可。</p>
<blockquote>
<p>注:以上代码基于 STC8H 手册"串口通信"章节及"中断系统"章节中的寄存器定义和示例编写。</p>
</blockquote>
<hr />
<p><a href="https://www.stcaimcu.com/plugin.php?id=twpx_xunsearch&mod=forum&q=STC8H8K64U%20%E4%B8%B2%E5%8F%A3%20%E5%8F%91%E9%80%81%2060%E5%AD%97%E8%8A%82&f=_all&m=yes&syn=yes&s=relevance&fid=0">🔍 搜索 "STC8H8K64U 串口 发送 60字节"</a></p>
<p><strong>📌 相关论坛讨论</strong></p>
<ol>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=10589&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&page=1&extra=#pid104712">请问8H8K64U的SPI连续发送数据的问题</a></li>
<li><a href="https://www.stcaimcu.com/forum.php?mod=viewthread&tid=9654&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&page=1&extra=#pid93836">STC8H8K64U是否支持 HID包发送实际长度数据?而不用固定64字节</a></li>
<li>一起学 AiCube之 AI8H8K64U应用, printf_usb("Hello World !\r\n");</li>
</ol>
<p>⏱️ 查找用时 13.4s | 总回答用时 32.6s</p>
STC8A8K64D4全球唯一ID(地址:0FDF9开始7个字节)
与EEPROM相同地址(地址:0FDF9)是否冲突?
做EEPROM就不能读全球唯一ID了吗?