论坛搜索结果太多,我直接基于手册资料来构建完整的方案。现在我已经掌握了足够的信息,下面给出完整的方案和代码。
STC8H4K64TLCD 电池供电超低功耗段码屏温湿度监测穿戴设备方案
一、系统方案概述
1.1 硬件架构
| 模块 |
器件 |
说明 |
| 主控 |
STC8H4K64TLCD |
内置段码LCD驱动(4COM×40SEG)、12位ADC、RTC、触摸按键 |
| 段码屏 |
4COM×SEG段码LCD |
显示温湿度、时间、电池电量 |
| 温湿度传感器 |
SHT30(推荐) |
I²C接口,精度±0.3°C/±2%RH,功耗极低 |
| 电池 |
锂聚合物电池 3.7V |
经LDO稳压至3.3V供电 |
| RTC晶振 |
32.768KHz |
外部晶振,RTC和LCD共用时钟源 |
| 触摸按键 |
1~2个触摸通道 |
用于唤醒/切换显示 |
1.2 功耗预算(典型值@3.3V)
| 工作状态 |
电流 |
说明 |
| 掉电模式(纯睡眠) |
0.4µA |
主时钟停振,所有外设关闭 |
| 掉电+RTC运行 |
~2.0µA |
RTC使用外部32.768K晶振 |
| 掉电+RTC+LCD保持 |
~3.5µA |
LCD使用外部32.768K晶振持续刷新 |
| 唤醒采样(约5ms) |
~2mA |
开启ADC/I²C读取传感器 |
| 平均功耗(5秒周期) |
~4µA |
200mAh电池可用~5.7年 |
二、主程序代码(main.c)
/*
* ============================================================
* STC8H4K64TLCD 超低功耗段码屏温湿度监测穿戴设备
*
* 硬件平台:STC8H4K64TLCD系列
* 工作频率:内部IRC 24MHz(唤醒后),掉电模式0.4µA
* 段码LCD:4COM×SEG,时钟源=外部32.768KHz(掉电时持续显示)
* 温湿度传感器:SHT30(I2C地址 0x44)
* 电池电压检测:ADC通道15(内部1.19V基准反推VCC)
* 唤醒方式:RTC秒中断(每5秒唤醒一次)
*
* 使用说明:
* 1. 在ISP下载时选择内部IRC频率为24MHz
* 2. 外部32.768KHz晶振接P1.7(XTALI)/P1.6(XTALO)
* 3. SHT30的SCL接P1.4,SDA接P1.5(I2C)
* 4. 段码LCD的COM0~COM3接P5.0/P5.1/P3.5/P3.6
* 5. SEG线根据实际段码屏定义连接
* 6. 触摸按键通道0接对应TS口(可选)
*
* 编译环境:Keil C51
* 头文件:stc8h.h(需包含STC8H4K64TLCD系列定义)
* ============================================================
*/
#include "stc8h.h"
#include "intrins.h"
/* ======================== 用户配置宏 ======================== */
/* 系统时钟 */
#define FOSC 24000000UL // 内部IRC频率 24MHz
/* 掉电唤醒周期(秒) */
#define SLEEP_CYCLE_S 5 // 每5秒唤醒一次采样
/* 电池电压检测阈值(mV) */
#define BAT_LOW_1 3300 // 第一档低电量(显示1格)
#define BAT_LOW_2 3600 // 第二档
#define BAT_LOW_3 3800 // 第三档
#define BAT_FULL 4000 // 满电(4格)
/* SHT30 I2C地址(7位地址左移1位) */
#define SHT30_ADDR 0x88 // ADDR引脚接GND时为0x44<<1
/* ======================== 段码屏映射定义 ======================== */
/*
* 假设段码屏布局(4COM×SEG):
* COM0: 数字8的a/b/c/d/e/f/g/dp段
* COM1: 数字8的a/b/c/d/e/f/g/dp段
* COM2: 数字8的a/b/c/d/e/f/g/dp段 + 图标
* COM3: 数字8的a/b/c/d/e/f/g/dp段 + 图标
*
* 实际段码映射需根据具体屏的引脚定义修改以下宏
*/
/* 段码位定义(每个COM对应一个字节,位0~7对应SEG0~SEG7) */
#define SEG_A 0x01
#define SEG_B 0x02
#define SEG_C 0x04
#define SEG_D 0x08
#define SEG_E 0x10
#define SEG_F 0x20
#define SEG_G 0x40
#define SEG_DP 0x80
/* 数字0~9的段码表(共阴极,1=亮) */
unsigned char code t_digit_seg[10] = {
0x3F, // 0: a,b,c,d,e,f
0x06, // 1: b,c
0x5B, // 2: a,b,d,e,g
0x4F, // 3: a,b,c,d,g
0x66, // 4: b,c,f,g
0x6D, // 5: a,c,d,f,g
0x7D, // 6: a,c,d,e,f,g
0x07, // 7: a,b,c
0x7F, // 8: a,b,c,d,e,f,g
0x6F // 9: a,b,c,d,f,g
};
/* 图标位定义(根据实际屏定义) */
#define ICON_BAT1 0x01 // 电池图标第1格
#define ICON_BAT2 0x02 // 电池图标第2格
#define ICON_BAT3 0x04 // 电池图标第3格
#define ICON_BAT4 0x08 // 电池图标第4格
#define ICON_HUMI 0x10 // 湿度图标
#define ICON_TEMP 0x20 // 温度图标
#define ICON_C 0x40 // 摄氏度符号
#define ICON_PERCENT 0x80 // 百分号
/* ======================== 全局变量 ======================== */
/* 温湿度数据 */
unsigned char g_temperature_int; // 温度整数部分(度)
unsigned char g_temperature_dec; // 温度小数部分(0.1度)
unsigned char g_humidity_int; // 湿度整数部分(%RH)
unsigned char g_humidity_dec; // 湿度小数部分(0.1%RH)
/* 电池电压 */
unsigned int g_bat_mv; // 电池电压(mV)
unsigned char g_bat_level; // 电池电量等级 0~4
/* RTC时间缓存 */
unsigned char g_rtc_hour;
unsigned char g_rtc_min;
unsigned char g_rtc_sec;
/* 系统标志 */
volatile unsigned char g_rtc_irq_flag; // RTC中断标志
volatile unsigned char g_wakeup_count; // 唤醒计数
/* I2C通信缓冲区 */
unsigned char g_i2c_txbuf[4];
unsigned char g_i2c_rxbuf[8];
/* ======================== 函数声明 ======================== */
/* 系统初始化 */
void System_Init(void);
void GPIO_Init(void);
void Clock_Init(void);
void LCD_Init(void);
void RTC_Init(void);
void ADC_Init(void);
void I2C_Init(void);
void TouchKey_Init(void);
/* 低功耗管理 */
void Enter_Sleep(void);
void Wakeup_Process(void);
/* 传感器驱动 */
unsigned char SHT30_Read(float *temp, float *humi);
unsigned char I2C_WriteRead(unsigned char addr, unsigned char *wbuf,
unsigned char wlen, unsigned char *rbuf,
unsigned char rlen);
/* ADC */
unsigned int ADC_Read(unsigned char channel);
/* 显示驱动 */
void LCD_Display_All(void);
void LCD_Display_Temperature(void);
void LCD_Display_Humidity(void);
void LCD_Display_Battery(void);
void LCD_Display_Time(void);
void LCD_Write_Digit(unsigned char com, unsigned char seg,
unsigned char digit, unsigned char dp);
void LCD_Write_Icon(unsigned char com, unsigned char seg,
unsigned char icon_mask);
/* 工具函数 */
void Delay1ms(unsigned int ms);
unsigned int Get_Battery_mV(void);
/* ======================== 主函数 ======================== */
void main(void)
{
float temperature, humidity;
unsigned char ret;
/* ---- 第一步:系统初始化 ---- */
System_Init();
/* ---- 第二步:首次上电初始化显示 ---- */
/* 读取传感器 */
ret = SHT30_Read(&temperature, &humidity);
if (ret == 0)
{
/* 转换并保存 */
if (temperature < 0)
{
g_temperature_int = 0;
g_temperature_dec = 0;
}
else
{
g_temperature_int = (unsigned char)temperature;
g_temperature_dec = (unsigned char)((temperature - g_temperature_int) * 10);
}
g_humidity_int = (unsigned char)humidity;
g_humidity_dec = (unsigned char)((humidity - g_humidity_int) * 10);
}
/* 读取电池电压 */
g_bat_mv = Get_Battery_mV();
/* 更新段码屏显示 */
LCD_Display_All();
/* ---- 第三步:主循环(掉电-唤醒-采样-显示-再掉电) ---- */
while (1)
{
/* 进入掉电模式 */
Enter_Sleep();
/* ---- 被RTC中断唤醒后,从这里继续执行 ---- */
/* 处理唤醒事件 */
Wakeup_Process();
/* 每 SLEEP_CYCLE_S 秒执行一次完整采样 */
if (g_wakeup_count >= 1)
{
g_wakeup_count = 0;
/* 读取温湿度传感器 */
ret = SHT30_Read(&temperature, &humidity);
if (ret == 0)
{
if (temperature < 0)
{
g_temperature_int = 0;
g_temperature_dec = 0;
}
else
{
g_temperature_int = (unsigned char)temperature;
g_temperature_dec = (unsigned char)((temperature - g_temperature_int) * 10);
}
g_humidity_int = (unsigned char)humidity;
g_humidity_dec = (unsigned char)((humidity - g_humidity_int) * 10);
}
/* 读取电池电压 */
g_bat_mv = Get_Battery_mV();
/* 更新段码屏显示 */
LCD_Display_All();
}
}
}
/* ======================== 系统初始化 ======================== */
void System_Init(void)
{
/* 使能访问扩展特殊功能寄存器(XFR) */
P_SW2 |= 0x80;
/* 设置IRCDB:掉电唤醒后等待16个时钟再给CPU供时钟 */
/* 手册要求:运行在33.1776MHz以下时,必须将IRCDB设置为0x10 */
IRCDB = 0x10;
/* 设置IAP_TPS(根据系统工作频率24MHz) */
IAP_TPS = 24;
/* 初始化GPIO */
GPIO_Init();
/* 初始化时钟 */
Clock_Init();
/* 初始化ADC */
ADC_Init();
/* 初始化I2C */
I2C_Init();
/* 初始化段码LCD */
LCD_Init();
/* 初始化RTC */
RTC_Init();
/* 初始化触摸按键(可选) */
// TouchKey_Init();
}
/* ======================== 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;
P6M0 = 0x00; P6M1 = 0x00;
P7M0 = 0x00; P7M1 = 0x00;
/*
* 注意:段码LCD的COM和SEG口由LCD模块自动控制,
* 不需要手动配置I/O模式。
* 但需要确保这些口没有被其他外设复用。
*/
/* I2C引脚(P1.4-SDA, P1.5-SCL)设置为开漏输出 */
/* 注意:P1.4和P1.5同时也是SEG1和SEG0的默认引脚 */
/* 如果与段码屏冲突,需通过LCDCFG2切换SEG0/SEG1到P7.6/P7.7 */
/* 这里假设SEG0/SEG1已切换到P7.6/P7.7,P1.4/P1.5用于I2C */
P1M0 |= 0x30; // P1.4,P1.5: 开漏输出
P1M1 |= 0x30;
}
/* ======================== 时钟初始化 ======================== */
void Clock_Init(void)
{
/*
* 系统时钟使用内部24MHz IRC(ISP下载时已设置)
* 外部32.768KHz晶振用于RTC和LCD
*/
/* 启动外部32.768KHz晶振 */
X32KCR = 0xC0; // ENX32K=1, GAIN32K=1(高增益,确保稳定起振)
while (!(X32KCR & 0x01)); // 等待X32KST=1,晶振稳定
/* 注意:此时系统时钟仍为内部24MHz IRC,无需切换 */
}
/* ======================== 段码LCD初始化 ======================== */
void LCD_Init(void)
{
unsigned int lcd_clk;
unsigned long comlen;
/*
* LCD时钟源选择:外部32.768KHz晶振(CLKSEL=10)
* 这样在掉电模式下LCD仍能持续刷新显示
* 前提是RTC也使用外部32.768KHz晶振(默认RTC启动后外部晶振继续工作)
*/
LCDCFG = 0x80; // CLKSEL[1:0]=10, VRLPSEL=0, VLCDSEL=000(0.65*VCC)
/*
* VLCD电压选择:根据段码屏的工作电压选择
* 电池供电建议选择0.65*VCC~0.75*VCC以降低功耗
* 如果VCC=3.3V,0.65*3.3V≈2.15V,适合大多数段码屏
*/
/* 使能COM0~COM3(4COM模式) */
COMON = 0x0F; // COM0,COM1,COM2,COM3使能
/* 使能需要用到的SEG线(根据实际段码屏定义) */
/* 假设使用SEG0~SEG15 */
SEGON1 = 0xFF; // SEG7~SEG0使能
SEGON2 = 0xFF; // SEG15~SEG8使能
// SEGON3 = 0x00; // 未使用的SEG保持关闭以省电
// SEGON4 = 0x00;
// SEGON5 = 0x00;
/*
* 配置LCD刷新率(帧速率)为60Hz
* LCD时钟 = 32768Hz
* COM数 = 4
* 公式:刷新率 = LCD时钟 / ((DBLEN + COMLEN + 1) × 2 × COM数)
* 60 = 32768 / ((DBLEN + COMLEN + 1) × 2 × 4)
* DBLEN + COMLEN + 1 = 32768 / (60 × 8) ≈ 68.27
* 取DBLEN=2, COMLEN=65
* 实际刷新率 = 32768 / ((2 + 65 + 1) × 8) = 32768 / 544 ≈ 60.2Hz
*/
DBLEN = 0x02; // 死区时间=2个LCD时钟
/* COMLEN = 65 = 0x41 */
COMLENL = 0x41; // COMLEN[7:0]
COMLENM = 0x00; // COMLEN[15:8]
COMLENH = 0x00; // COMLEN[19:16]
/* 配置闪烁率(不使用闪烁功能,设为默认) */
BLANKRATE = 0x80; // 默认值
/* 设置显示模式为普通显示模式 */
LCDCR = 0x01; // ACTMODE[1:0]=00(普通模式), ENLCD=1(使能LCD)
/*
* 注意:LCD使能后,COM和SEG引脚自动被LCD模块接管,
* 这些引脚不能再作为普通GPIO使用。
*/
}
/* ======================== RTC初始化 ======================== */
void RTC_Init(void)
{
/*
* RTC使用外部32.768KHz晶振作为时钟源
* 这样在掉电模式下RTC继续运行,且LCD也能继续刷新
*/
/* 设置RTC时钟源为外部32.768KHz */
RTCCFG = 0x00; // RTCCKS=0(外部32.768KHz)
/* 设置初始时间:2025年1月1日 00:00:00 */
INIYEAR = 25; // 2025年
INIMONTH = 1; // 1月
INIDAY = 1; // 1日
INIHOUR = 0; // 0时
INIMIN = 0; // 0分
INISEC = 0; // 0秒
INISSEC = 0; // 0/128秒
/* 触发RTC初始化,将初始值装载到RTC计数器 */
RTCCFG |= 0x01; // SETRTC=1
while (RTCCFG & 0x01); // 等待初始化完成(硬件自动清0)
/* 清除所有RTC中断标志 */
RTCIF = 0;
/* 使能RTC秒中断(用于定时唤醒) */
RTCIEN = 0x08; // ESECI=1(使能秒中断)
/* 启动RTC */
RTCCR = 0x01; // RUNRTC=1
/* 使能RTC中断(总中断在进入主循环前开启) */
/* RTC中断向量在main中通过EA控制 */
}
/* ======================== ADC初始化 ======================== */
void ADC_Init(void)
{
/*
* ADC用于测量电池电压
* 使用通道15(内部1.19V基准),通过反推计算VCC
* 如果VCC直接接电池经LDO稳压,则VCC=电池电压
*/
/* 配置ADC时钟:24MHz/2/12 = 1MHz */
ADCCFG = 0x0B; // RESFMT=0(左对齐), SPEED=1011(12分频)
/* 配置ADC时序 */
ADCTIM = 0x2A; // CSSETUP=0, CSHOLD=01, SMPDUTY=01010(默认值)
/* 注意:ADC电源在需要采样时才打开,以降低功耗 */
}
/* ======================== I2C初始化 ======================== */
void I2C_Init(void)
{
/*
* 使用硬件I2C模块与SHT30通信
* I2C引脚:SDA(P1.4), SCL(P1.5)
*/
/* 配置I2C时钟为约400KHz(快速模式) */
/* I2CCFG寄存器在XFR空间,地址FE80H */
/* 注意:P_SW2已在System_Init中使能 */
/* I2C时钟 = SYSCLK / 2 / (I2CCFG + 1) */
/* 24MHz / 2 / (29 + 1) = 400KHz */
I2CCFG = 29; // 设置I2C时钟分频
/* 使能I2C主机模式 */
I2CCFG |= 0x80; // ENI2C=1
}
/* ======================== 低功耗管理 ======================== */
void Enter_Sleep(void)
{
/*
* 进入掉电模式前的准备工作:
* 1. 关闭ADC电源(如果之前打开了)
* 2. 关闭不必要的外设时钟
* 3. 设置I/O口为低功耗状态
* 4. 进入掉电模式
*/
/* 关闭ADC电源以省电 */
ADC_CONTR &= ~0x80; // ADC_POWER=0
/*
* I/O口省电设置:
* 进入掉电模式前,将所有未使用的I/O口设置为高阻输入,
* 并关闭数字输入通道,防止浮空电平导致额外功耗。
*
* 注意:段码LCD的COM/SEG口由LCD模块控制,不需要手动设置。
* I2C引脚(P1.4/P1.5)保持开漏输出。
*/
/* 关闭未使用I/O口的数字输入(根据实际硬件调整) */
/* P0IE = 0x00; */ // 如果P0口未使用
/* P1IE &= ~0xCF; */ // 保留I2C引脚的数字输入
/* ... */
/*
* 进入掉电模式(主时钟停振)
* 注意:RTC使用外部32.768KHz晶振,掉电后继续运行
* LCD使用外部32.768KHz晶振,掉电后继续刷新
*/
PCON = 0x02; // PD=1,进入掉电模式
/*
* 掉电模式被唤醒后,MCU从这条语句的下一条开始执行
* 手册建议在掉电语句后加7~9个NOP
*/
_nop_(); _nop_(); _nop_(); _nop_();
_nop_(); _nop_(); _nop_(); _nop_();
_nop_();
}
void Wakeup_Process(void)
{
/*
* 唤醒后的处理:
* 1. 重新使能XFR访问(掉电唤醒后P_SW2可能被复位)
* 2. 重新配置系统时钟相关寄存器
* 3. 清除RTC中断标志
*/
/* 重新使能XFR访问 */
P_SW2 |= 0x80;
/* 重新设置IRCDB(掉电唤醒后寄存器复位) */
IRCDB = 0x10;
/* 重新设置IAP_TPS */
IAP_TPS = 24;
/* 清除RTC中断标志 */
if (g_rtc_irq_flag)
{
g_rtc_irq_flag = 0;
RTCIF = 0; // 清除所有RTC中断标志
}
/* 增加唤醒计数 */
g_wakeup_count++;
}
/* ======================== SHT30温湿度传感器驱动 ======================== */
/*
* SHT30通信协议:
* 1. 发送测量命令:0x2C 0x06(高重复性,时钟拉伸使能)
* 2. 等待约15ms(实际测量时间)
* 3. 读取6字节数据:温度高8位、温度低8位、CRC、湿度高8位、湿度低8位、CRC
* 4. 温度值 = -45 + 175 * (raw_temp / 65535)
* 5. 湿度值 = 100 * (raw_humi / 65535)
*/
unsigned char SHT30_Read(float *temp, float *humi)
{
unsigned int raw_temp, raw_humi;
unsigned char ret;
/* 发送测量命令 */
g_i2c_txbuf[0] = 0x2C; // 测量命令高字节
g_i2c_txbuf[1] = 0x06; // 测量命令低字节(高重复性)
ret = I2C_WriteRead(SHT30_ADDR, g_i2c_txbuf, 2, NULL, 0);
if (ret != 0) return 1;
/* 等待测量完成(至少15ms) */
Delay1ms(20);
/* 读取6字节数据 */
ret = I2C_WriteRead(SHT30_ADDR | 0x01, NULL, 0, g_i2c_rxbuf, 6);
if (ret != 0) return 2;
/* 解析温度值 */
raw_temp = ((unsigned int)g_i2c_rxbuf[0] << 8) | g_i2c_rxbuf[1];
/* CRC校验(g_i2c_rxbuf[2]),此处简化处理,略过CRC校验 */
/* 解析湿度值 */
raw_humi = ((unsigned int)g_i2c_rxbuf[3] << 8) | g_i2c_rxbuf[4];
/* CRC校验(g_i2c_rxbuf[5]),此处简化处理,略过CRC校验 */
/* 转换为实际温湿度值 */
/* 温度 = -45 + 175 * raw / 65535 */
/* 使用定点数计算:扩大100倍,保留两位小数 */
{
unsigned long calc;
/* 温度计算(保留1位小数,即扩大10倍) */
calc = (unsigned long)raw_temp * 1750UL; // 1750 = 175 * 10
calc = calc / 65535UL;
if (calc >= 450) // 450 = 45 * 10
{
calc = calc - 450;
}
else
{
calc = 0; // 低于-45°C,显示0
}
*temp = (float)calc / 10.0f;
/* 湿度计算(保留1位小数) */
calc = (unsigned long)raw_humi * 1000UL; // 1000 = 100 * 10
calc = calc / 65535UL;
*humi = (float)calc / 10.0f;
}
return 0;
}
/* ======================== I2C读写函数(软件模拟或硬件) ======================== */
/*
* 使用STC8H硬件I2C模块进行读写
* 参数:
* addr: 7位设备地址左移1位(含读写位)
* wbuf: 发送数据缓冲区
* wlen: 发送数据长度
* rbuf: 接收数据缓冲区
* rlen: 接收数据长度
* 返回:0=成功,非0=失败
*/
unsigned char I2C_WriteRead(unsigned char addr, unsigned char *wbuf,
unsigned char wlen, unsigned char *rbuf,
unsigned char rlen)
{
unsigned char i;
unsigned int timeout;
/* 发送起始信号 + 设备地址(写) */
I2CMSCR = 0x00; // 清除I2C主机状态
I2CTXD = addr & 0xFE; // 设备地址(写操作,bit0=0)
I2CMSCR = 0x01; // 发送起始信号+地址
timeout = 0;
while (!(I2CMSST & 0x40)) // 等待MSIF=1
{
if (++timeout > 50000) return 1;
}
if (I2CMSST & 0x01) return 2; // 检查ACK
I2CMSST = 0x40; // 清除MSIF
/* 发送数据 */
for (i = 0; i < wlen; i++)
{
I2CTXD = wbuf[i];
I2CMSCR = 0x02; // 发送数据
timeout = 0;
while (!(I2CMSST & 0x40))
{
if (++timeout > 50000) return 3;
}
if (I2CMSST & 0x01) return 4;
I2CMSST = 0x40;
}
/* 如果需要读取数据 */
if (rlen > 0)
{
/* 发送重复起始信号 + 设备地址(读) */
I2CTXD = addr | 0x01; // 设备地址(读操作,bit0=1)
I2CMSCR = 0x03; // 发送重复起始信号+地址
timeout = 0;
while (!(I2CMSST & 0x40))
{
if (++timeout > 50000) return 5;
}
if (I2CMSST & 0x01) return 6;
I2CMSST = 0x40;
/* 读取数据 */
for (i = 0; i < rlen; i++)
{
if (i == rlen - 1)
{
I2CMSCR = 0x05; // 读取数据+NACK(最后一字节)
}
else
{
I2CMSCR = 0x04; // 读取数据+ACK
}
timeout = 0;
while (!(I2CMSST & 0x40))
{
if (++timeout > 50000) return 7;
}
I2CMSST = 0x40;
rbuf[i] = I2CRXD;
}
}
/* 发送停止信号 */
I2CMSCR = 0x06;
timeout = 0;
while (!(I2CMSST & 0x40))
{
if (++timeout > 50000) return 8;
}
I2CMSST = 0x40;
return 0;
}
/* ======================== ADC读取 ======================== */
unsigned int ADC_Read(unsigned char channel)
{
unsigned int result;
/* 打开ADC电源 */
ADC_CONTR = 0x80; // ADC_POWER=1
Delay1ms(1); // 等待ADC电源稳定(手册建议约1ms)
/* 选择通道并启动转换 */
ADC_CONTR = 0x80 | 0x40 | channel; // POWER=1, START=1, CHS=channel
/* 等待转换完成 */
while (!(ADC_CONTR & 0x20)); // 等待ADC_FLAG=1
/* 读取结果(左对齐模式,12位结果) */
/* RESFMT=0时,ADC_RES[7:0]为高8位,ADC_RESL[7:4]为低4位 */
result = ((unsigned int)ADC_RES << 4) | (ADC_RESL >> 4);
/* 清除标志 */
ADC_CONTR &= ~0x20; // 清除ADC_FLAG
/* 关闭ADC电源以省电 */
ADC_CONTR &= ~0x80; // ADC_POWER=0
return result;
}
/* ======================== 获取电池电压 ======================== */
unsigned int Get_Battery_mV(void)
{
unsigned int adc_value;
unsigned long vcc_mv;
/*
* 使用ADC通道15测量内部1.19V基准电压
* 通过反推计算VCC(即电池电压)
* VCC = 1.19V * 4096 / ADC_Value
* 其中ADC_Value是12位ADC读数
*/
adc_value = ADC_Read(0x0F); // 通道15(内部1.19V基准)
if (adc_value == 0) adc_value = 1; // 防止除零
/* VCC(mV) = 1190 * 4096 / adc_value */
vcc_mv = 1190UL * 4096UL / adc_value;
/* 计算电池电量等级 */
if (vcc_mv >= BAT_FULL)
{
g_bat_level = 4;
}
else if (vcc_mv >= BAT_LOW_3)
{
g_bat_level = 3;
}
else if (vcc_mv >= BAT_LOW_2)
{
g_bat_level = 2;
}
else if (vcc_mv >= BAT_LOW_1)
{
g_bat_level = 1;
}
else
{
g_bat_level = 0;
}
return (unsigned int)vcc_mv;
}
/* ======================== 段码LCD显示函数 ======================== */
/*
* 段码LCD显示RAM映射:
* C0SEGV0 (FB90H): COM0, SEG7~SEG0
* C0SEGV1 (FB91H): COM0, SEG15~SEG8
* C1SEGV0 (FB98H): COM1, SEG7~SEG0
* C1SEGV1 (FB99H): COM1, SEG15~SEG8
* C2SEGV0 (FBA0H): COM2, SEG7~SEG0
* C2SEGV1 (FBA1H): COM2, SEG15~SEG8
* C3SEGV0 (FBA8H): COM3, SEG7~SEG0
* C3SEGV1 (FBA9H): COM3, SEG15~SEG8
*
* 每个位对应一个SEG线,1=点亮,0=熄灭
*/
/* 更新所有显示内容 */
void LCD_Display_All(void)
{
LCD_Display_Temperature();
LCD_Display_Humidity();
LCD_Display_Battery();
// LCD_Display_Time(); // 如果需要显示时间,取消注释
}
/* 显示温度(假设使用COM0和COM1显示两位整数+一位小数) */
void LCD_Display_Temperature(void)
{
unsigned char temp_int, temp_dec;
unsigned char seg_data;
temp_int = g_temperature_int;
temp_dec = g_temperature_dec;
/*
* 假设布局:
* COM0: 十位数字(SEG0~SEG6为a~g,SEG7为dp)
* COM1: 个位数字(SEG0~SEG6为a~g,SEG7为dp)
* COM2: 小数位(SEG0~SEG6为a~g)
* COM3: 温度图标和°C符号
*/
/* COM0: 十位(如果温度>=10,显示十位数字;否则熄灭) */
if (temp_int >= 10)
{
seg_data = t_digit_seg[temp_int / 10];
}
else
{
seg_data = 0x00; // 十位熄灭(或显示"-")
}
C0SEGV0 = seg_data; // 写入COM0的SEG7~SEG0
/* COM1: 个位 */
seg_data = t_digit_seg[temp_int % 10];
C1SEGV0 = seg_data; // 写入COM1的SEG7~SEG0
/* COM2: 小数位 + 小数点 */
seg_data = t_digit_seg[temp_dec];
seg_data |= SEG_DP; // 点亮小数点(表示小数位)
C2SEGV0 = seg_data; // 写入COM2的SEG7~SEG0
/* COM3: 温度图标和°C符号 */
/* 假设ICON_TEMP在SEG0, ICON_C在SEG1 */
seg_data = ICON_TEMP | ICON_C;
C3SEGV0 = seg_data;
}
/* 显示湿度 */
void LCD_Display_Humidity(void)
{
unsigned char humi_int, humi_dec;
unsigned char seg_data;
humi_int = g_humidity_int;
humi_dec = g_humidity_dec;
/*
* 假设使用与温度相同的布局,但通过分时显示或不同位置
* 这里简化:使用COM0~COM3显示湿度
*/
/* COM0: 十位 */
if (humi_int >= 10)
{
seg_data = t_digit_seg[humi_int / 10];
}
else
{
seg_data = 0x00;
}
C0SEGV1 = seg_data; // 使用SEG15~SEG8(不同位置)
/* COM1: 个位 */
seg_data = t_digit_seg[humi_int % 10];
C1SEGV1 = seg_data;
/* COM2: 小数位 */
seg_data = t_digit_seg[humi_dec];
C2SEGV1 = seg_data;
/* COM3: 湿度图标和百分号 */
seg_data = ICON_HUMI | ICON_PERCENT;
C3SEGV1 = seg_data;
}
/* 显示电池电量 */
void LCD_Display_Battery(void)
{
unsigned char bat_icon;
/* 根据电量等级显示电池图标 */
switch (g_bat_level)
{
case 0: bat_icon = 0x00; break; // 无格(需充电)
case 1: bat_icon = ICON_BAT1; break; // 1格
case 2: bat_icon = ICON_BAT1 | ICON_BAT2; break; // 2格
case 3: bat_icon = ICON_BAT1 | ICON_BAT2 | ICON_BAT3; break; // 3格
case 4: bat_icon = ICON_BAT1 | ICON_BAT2 | ICON_BAT3 | ICON_BAT4; break; // 4格
default: bat_icon = 0x00; break;
}
/* 假设电池图标在COM3的SEG8~SEG15区域 */
/* 这里使用C3SEGV1的高4位 */
C3SEGV1 = (C3SEGV1 & 0x0F) | bat_icon;
}
/* 显示时间(可选) */
void LCD_Display_Time(void)
{
unsigned char hour, min;
unsigned char seg_data;
/* 读取RTC时间 */
/* 注意:RTC寄存器在XFR空间,需要P_SW2已使能 */
hour = RTCHOUR;
min = RTCMIN;
/*
* 假设时间显示在另外的SEG区域
* 这里仅作示例
*/
/* COM0: 小时十位 */
seg_data = t_digit_seg[hour / 10];
// C0SEGV2 = seg_data; // 假设使用SEG23~SEG16
/* COM1: 小时个位 */
seg_data = t_digit_seg[hour % 10];
// C1SEGV2 = seg_data;
/* COM2: 分钟十位 */
seg_data = t_digit_seg[min / 10];
// C2SEGV2 = seg_data;
/* COM3: 分钟个位 */
seg_data = t_digit_seg[min % 10];
// C3SEGV2 = seg_data;
}
/* ======================== RTC中断服务函数 ======================== */
/*
* RTC中断向量号:根据STC8H手册,RTC中断向量为 0x00D3
* 中断号:RTC_VECTOR = 26(对应中断号26,地址0x00D3)
*/
void RTC_ISR(void) interrupt 26
{
unsigned char rtcif;
/* 读取中断标志 */
rtcif = RTCIF;
/* 清除所有中断标志 */
RTCIF = 0;
/* 如果是秒中断 */
if (rtcif & 0x08) // SECIF=1
{
g_rtc_irq_flag = 1;
}
}
/* ======================== 延时函数 ======================== */
void Delay1ms(unsigned int ms)
{
unsigned int i, j;
/* 24MHz下,约1ms */
for (i = 0; i < ms; i++)
{
/* 空循环约24000次 ≈ 1ms */
for (j = 0; j < 600; j++)
{
_nop_();
}
}
}
三、关键设计说明
3.1 超低功耗实现原理
| 技术手段 |
实现方式 |
功耗贡献 |
| 掉电模式 |
PCON = 0x02 主时钟停振 |
0.4µA |
| RTC独立运行 |
外部32.768KHz晶振,掉电后继续计时 |
~1.5µA |
| LCD独立刷新 |
LCD时钟源选外部32.768KHz,掉电后持续显示 |
~1.5µA |
| 间歇工作 |
每5秒唤醒一次,采样耗时约5ms |
平均<1µA |
| I/O省电 |
未用I/O设高阻+关数字输入 |
0 |
| ADC按需开启 |
仅在采样时开ADC电源 |
0(休眠时) |
总平均功耗 ≈ 0.4 + 1.5 + 1.5 + (2mA × 5ms / 5s) ≈ 4µA
3.2 段码LCD驱动要点
根据手册"段式LCD液晶驱动,4COM×40SEG"章节:
- COM引脚:COM0=P5.0, COM1=P5.1, COM2=P3.5, COM3=P3.6
- SEG引脚:SEG0
SEG39分布在P0P7口
- 显示RAM:C0SEGV0~C3SEGV4,每个COM有5个字节对应40个SEG
- 时钟源:推荐使用外部32.768KHz(CLKSEL=10),掉电模式可继续刷新
- 刷新率:建议55~60Hz,通过DBLEN和COMLEN寄存器配置
3.3 电池电压检测
使用ADC通道15(内部1.19V基准)反推VCC:
VCC(mV) = 1190 × 4096 / ADC_Value
此方法无需外部电阻分压,不增加额外功耗。
四、硬件设计建议
4.1 电源电路
电池(3.7V) → LDO(3.3V) → VCC
↓
VCC直接给MCU供电
ADC_VRef+ 接 VCC
4.2 外部32.768KHz晶振电路
P1.7(XTALI) ──┬── 32.768KHz ──┬── P1.6(XTALO)
│ │
10pF 10pF
│ │
GND GND
4.3 SHT30连接
SHT30:
VDD ── 3.3V
GND ── GND
SCL ── P1.5 (需4.7K上拉至3.3V)
SDA ── P1.4 (需4.7K上拉至3.3V)
ADDR ── GND (I2C地址 0x44)
4.4 段码LCD连接
COM0 ── P5.0
COM1 ── P5.1
COM2 ── P3.5
COM3 ── P3.6
SEG0~SEG39 ── 根据实际段码屏定义连接
五、ISP下载设置
在STC-ISP下载软件中:
- 芯片型号:选择 STC8H4K64TLCD 系列
- IRC频率:选择 24MHz
- 硬件选项:
- ✅ 允许低压复位(禁止低压中断)→ 取消勾选(使用软件检测电池电压)
- ✅ 上电复位使用较长时间
- ✅ 内部IRC时钟选择
- 下载时:P3.2接地进入下载模式
如需进一步调整显示布局、增加触摸按键唤醒、或修改采样周期,请告知具体需求,我可以继续完善代码。
🔍 搜索 "用 STC8H4K64TLCD 做一个电池供电超低功耗段码屏温湿度监测穿戴设备方案 并含详细程序注解"
📌 相关论坛讨论
- STC8H4K64TLCD 怎么用外部晶振40M驱动LCD段码屏
- STC单片机在智能水表里的应用 STC8H4K64TLCD
- 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
⏱️ 查找用时 20.2s | 总回答用时 96.4s