好的,我已经掌握了所有必要的信息。现在让我编写完整的数显压力表程序。
根据STC32G8K64-33A-LQFP32的管脚分配,我规划如下硬件接口:
- ADC输入:P1.0 (ADC通道0) — 接压力传感器模拟输出
- I2C总线:P1.4(SDA), P1.5(SCL) — 接0.96寸OLED显示屏(SSD1306)
- 串口1:P3.0(RxD), P3.1(TxD) — 调试/通信
- 按键:P3.2 — 模式切换/归零按键
- LED指示:P3.3 — 超压报警指示
现在开始编写完整程序:
/******************************************************************************
* 数显压力表程序范例
* 芯片型号:STC32G8K64-33A-LQFP32
* 开发环境:Keil C251
* 功能说明:
* 1. 使用ADC通道0(P1.0)采集压力传感器模拟电压
* 2. 通过ADC第15通道(内部1.19V参考源)反推VCC,实现电源电压自校准
* 3. 使用I2C驱动0.96寸OLED(SSD1306)显示实时压力值
* 4. 串口1(P3.0/P3.1)输出压力数据用于调试
* 5. 按键P3.2实现峰值保持/清零功能
* 6. P3.3驱动LED实现超压报警指示
*
* 硬件连接:
* P1.0 - 压力传感器模拟输入 (ADC通道0)
* P1.4 - I2C_SDA (OLED)
* P1.5 - I2C_SCL (OLED)
* P3.0 - UART1_RxD (调试)
* P3.1 - UART1_TxD (调试)
* P3.2 - 按键输入 (模式切换/峰值清零)
* P3.3 - 报警LED输出 (推挽,高电平点亮)
*
* 使用说明:
* 1. 在STC-ISP软件中选择内部IRC时钟 33.1776MHz
* 2. 下载时选择"使用内部高频IRC时钟"
* 3. 压力传感器输出0~3.3V对应0~满量程压力
* 4. 程序中 PRESSURE_MAX 宏定义满量程压力值(单位:kPa)
* 5. 程序中 PRESSURE_FULL_SCALE_V 宏定义满量程对应电压值
******************************************************************************/
#include "stc32g.h"
#include "intrins.h"
#include "stdio.h"
/**************************** 用户配置宏定义 ********************************/
#define PRESSURE_MAX 1000 // 满量程压力值 (单位: kPa)
#define PRESSURE_FULL_SCALE_V 3300 // 满量程对应电压值 (单位: mV)
#define OVER_PRESSURE_THRESHOLD 900 // 超压报警阈值 (单位: kPa)
#define ADC_SAMPLE_COUNT 8 // ADC采样平均次数
#define DISPLAY_UPDATE_MS 200 // 显示更新周期 (ms)
#define SERIAL_OUTPUT_MS 1000 // 串口输出周期 (ms)
/**************************** OLED I2C 地址定义 ******************************/
#define OLED_ADDR 0x78 // SSD1306 I2C地址 (7位地址0x3C左移1位)
/**************************** 全局变量定义 ***********************************/
unsigned char g_display_buffer[8]; // 显示缓冲区
unsigned int g_pressure_value = 0; // 当前压力值 (单位: kPa * 10, 保留1位小数)
unsigned int g_peak_pressure = 0; // 峰值压力值
unsigned char g_mode = 0; // 显示模式: 0-实时值, 1-峰值
unsigned char g_over_pressure_flag = 0; // 超压标志
unsigned int g_adc_raw_value = 0; // ADC原始值
unsigned int g_vcc_mv = 3300; // 实测VCC电压 (mV)
unsigned int g_tick_count = 0; // 系统滴答计数
/**************************** 函数声明 ***************************************/
void delay_ms(unsigned int ms);
void delay_us(unsigned int us);
void System_Init(void);
void GPIO_Init(void);
void Timer2_Init(void);
void ADC_Init(void);
void I2C_Init(void);
void UART1_Init(void);
unsigned int ADC_Read(unsigned char channel);
unsigned int ADC_GetPressure(void);
void I2C_Start(void);
void I2C_Stop(void);
unsigned char I2C_SendByte(unsigned char dat);
void OLED_WriteCmd(unsigned char cmd);
void OLED_WriteData(unsigned char dat);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str);
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len);
void Display_Update(void);
void UART1_SendString(unsigned char *str);
/**************************** 主函数 *****************************************/
void main(void)
{
unsigned int adc_bg; // 内部1.19V参考源的ADC读数
unsigned int adc_ch0; // 通道0的ADC读数
unsigned long temp; // 临时计算变量
unsigned int i;
System_Init(); // 系统初始化
GPIO_Init(); // I/O口初始化
Timer2_Init(); // 定时器2初始化 (1ms滴答)
ADC_Init(); // ADC初始化
I2C_Init(); // I2C初始化
UART1_Init(); // 串口1初始化
OLED_Init(); // OLED初始化
OLED_Clear(); // 清屏
OLED_ShowString(0, 0, "Pressure:"); // 显示标题
EA = 1; // 开启总中断
while(1)
{
/* ---- 步骤1: 读取ADC第15通道(内部1.19V参考源) ---- */
adc_bg = ADC_Read(15);
/* ---- 步骤2: 读取ADC通道0(压力传感器) ---- */
adc_ch0 = ADC_Read(0);
/* ---- 步骤3: 反推VCC电压 ---- */
/* 公式: VCC = 1.19V * 4096 / adc_bg */
/* 结果单位: V, 这里放大1000倍转为mV */
if(adc_bg != 0)
{
temp = (unsigned long)1190 * 4096 / adc_bg;
g_vcc_mv = (unsigned int)temp;
}
/* ---- 步骤4: 计算压力传感器输入电压(mV) ---- */
/* 公式: Vin = VCC * adc_ch0 / 4096 */
temp = (unsigned long)g_vcc_mv * adc_ch0 / 4096;
/* ---- 步骤5: 将电压转换为压力值 ---- */
/* 公式: Pressure = Vin / FULL_SCALE_V * PRESSURE_MAX */
/* 结果放大10倍保留1位小数 */
g_pressure_value = (unsigned int)((unsigned long)temp * PRESSURE_MAX * 10 / PRESSURE_FULL_SCALE_V);
/* ---- 步骤6: 更新峰值 ---- */
if(g_pressure_value > g_peak_pressure)
{
g_peak_pressure = g_pressure_value;
}
/* ---- 步骤7: 超压判断 ---- */
if(g_pressure_value >= OVER_PRESSURE_THRESHOLD * 10)
{
g_over_pressure_flag = 1;
P33 = 1; // 点亮报警LED
}
else
{
g_over_pressure_flag = 0;
P33 = 0; // 熄灭报警LED
}
/* ---- 步骤8: 按键检测 (P3.2, 按下为低电平) ---- */
if(P32 == 0)
{
delay_ms(20); // 消抖
if(P32 == 0)
{
/* 短按: 切换显示模式 */
g_mode = !g_mode;
/* 长按(>1秒): 峰值清零 */
for(i = 0; i < 50; i++)
{
delay_ms(20);
if(P32 == 1)
{
break; // 提前松开,短按处理
}
}
if(i >= 50) // 确实长按了1秒
{
g_peak_pressure = 0; // 峰值清零
g_mode = 0; // 切回实时模式
}
/* 等待按键松开 */
while(P32 == 0);
}
}
/* ---- 步骤9: 更新显示 (每200ms) ---- */
if(g_tick_count % DISPLAY_UPDATE_MS == 0)
{
Display_Update();
}
/* ---- 步骤10: 串口输出 (每1000ms) ---- */
if(g_tick_count % SERIAL_OUTPUT_MS == 0)
{
unsigned char buf[40];
sprintf(buf, "P=%ukPa PK=%ukPa VCC=%umV\r\n",
g_pressure_value / 10, g_peak_pressure / 10, g_vcc_mv);
UART1_SendString(buf);
}
delay_ms(10); // 主循环延时,避免过于频繁
}
}
/**************************** 系统初始化 *************************************/
void System_Init(void)
{
P0M0 = 0x00; P0M1 = 0x00; // P0: 准双向口
P1M0 = 0x00; P1M1 = 0x00; // P1: 暂设准双向,ADC通道和高阻后续单独设置
P2M0 = 0x00; P2M1 = 0x00; // P2: 准双向口
P3M0 = 0x00; P3M1 = 0x00; // P3: 暂设准双向
P4M0 = 0x00; P4M1 = 0x00; // P4: 准双向口
P5M0 = 0x00; P5M1 = 0x00; // P5: 准双向口
EAXFR = 1; // 使能访问XFR扩展寄存器
CKCON = 0x00; // 外部数据总线速度最快
WTST = 0x00; // 程序代码等待参数为0,CPU执行速度最快
/* 内部IRC时钟已在ISP下载时设置好,此处无需再配置 */
/* 注意: 如果使用省电模式,需要设置 IRCDB = 0x10 */
}
/**************************** I/O口初始化 ************************************/
void GPIO_Init(void)
{
/* P1.0 - ADC输入通道0,需设置为高阻输入模式 */
/* P1M0.0 = 0, P1M1.0 = 1 => 高阻输入 */
P1M0 &= ~0x01; // P1.0: M0=0
P1M1 |= 0x01; // P1.0: M1=1 => 高阻输入
/* P1.4 - I2C_SDA, P1.5 - I2C_SCL */
/* I2C总线使用开漏模式,需外接上拉电阻(4.7kΩ) */
/* 开漏模式: M0=1, M1=1 */
P1M0 |= 0x30; // P1.4,P1.5: M0=1
P1M1 |= 0x30; // P1.4,P1.5: M1=1 => 开漏模式
/* P3.0 - UART1_RxD (输入), P3.1 - UART1_TxD (推挽输出) */
P3M0 &= ~0x01; // P3.0: M0=0
P3M1 |= 0x01; // P3.0: M1=1 => 高阻输入
P3M0 |= 0x02; // P3.1: M0=1
P3M1 &= ~0x02; // P3.1: M1=0 => 推挽输出
/* P3.2 - 按键输入 (准双向口,内部弱上拉) */
P3M0 &= ~0x04; // P3.2: M0=0
P3M1 &= ~0x04; // P3.2: M1=0 => 准双向口
/* P3.3 - 报警LED输出 (推挽输出) */
P3M0 |= 0x08; // P3.3: M0=1
P3M1 &= ~0x08; // P3.3: M1=0 => 推挽输出
P33 = 0; // 初始熄灭
}
/**************************** 定时器2初始化 (1ms滴答) ************************/
void Timer2_Init(void)
{
/* 定时器2: 16位自动重装载模式,1T模式 */
/* 系统时钟 33.1776MHz */
/* 定时周期 = (65536 - [T2H,T2L]) / (SYSclk / (TM2PS+1)) */
/* 设 TM2PS = 0 (不分频), 1ms = (65536 - N) / 33.1776MHz */
/* N = 65536 - 33177.6 ≈ 65536 - 33178 = 32358 = 0x7E66 */
TM2PS = 0x00; // 预分频=1 (不分频)
T2L = 0x66; // 低字节
T2H = 0x7E; // 高字节 => 重载值 0x7E66
AUXR &= ~0x10; // T2x12=0, 12T模式
/* 重新计算: 12T模式, 1ms = (65536-N)*12/33.1776MHz */
/* N = 65536 - 33177600/12000 = 65536 - 2764.8 ≈ 65536 - 2765 = 62771 = 0xF533 */
T2L = 0x33; // 低字节
T2H = 0xF5; // 高字节 => 重载值 0xF533
AUXR |= 0x10; // T2R=1, 启动定时器2
IE2 |= 0x04; // 使能定时器2中断 (ET2=1)
}
/**************************** 定时器2中断服务函数 ****************************/
void Timer2_ISR(void) interrupt 12
{
/* 定时器2中断,每1ms进入一次 */
g_tick_count++;
}
/**************************** ADC初始化 **************************************/
void ADC_Init(void)
{
/* ADC配置:
* - 12位分辨率
* - 右对齐格式 (RESFMT=1)
* - ADC时钟 = SYSclk/2/(SPEED+1)
* 33.1776MHz / 2 / (15+1) ≈ 1.036MHz (接近但不超过800KHz, 调整SPEED)
* 33.1776MHz / 2 / (20+1) ≈ 790KHz (符合要求)
*/
ADCCFG = 0x20; // RESFMT=1 (右对齐), SPEED=0 (先设最小)
ADCCFG |= 0x14; // SPEED=20 (0x14) => 33.1776M/2/21≈790KHz
/* ADC时序控制 */
ADCTIM = 0x3F; // CSSETUP=0, CSHOLD=01, SMPDUTY=11111(31)
// 采样时间足够长,保证精度
/* 打开ADC电源 */
ADC_CONTR = 0x80; // ADC_POWER=1, 其他位为0
delay_ms(2); // 等待ADC电源稳定 (>1ms)
}
/**************************** ADC读取函数 ************************************/
unsigned int ADC_Read(unsigned char channel)
{
unsigned int result = 0;
unsigned char i;
/* 选择ADC通道 (channel: 0~15) */
ADC_CONTR = (ADC_CONTR & 0xf0) | (channel & 0x0f);
/* 多次采样取平均,提高精度 */
for(i = 0; i < ADC_SAMPLE_COUNT; i++)
{
/* 启动ADC转换 */
ADC_CONTR |= 0x40; // ADC_START=1
/* 等待转换完成 */
while(!(ADC_CONTR & 0x20)); // 等待ADC_FLAG=1
ADC_CONTR &= ~0x20; // 清除ADC_FLAG (软件清零)
/* 读取转换结果 (右对齐格式) */
/* ADC_RES[7:0] 高8位, ADC_RESL[7:0] 低8位, 但12位右对齐时: */
/* ADC_RES[3:0] 为高4位, ADC_RESL[7:0] 为低8位 */
result += ((unsigned int)ADC_RES << 8) | ADC_RESL;
}
/* 返回平均值 */
return result / ADC_SAMPLE_COUNT;
}
/**************************** I2C初始化 **************************************/
void I2C_Init(void)
{
/* I2C配置为主机模式 */
/* 总线速度 = SYSCLK / 2 / (MSSPEED*2 + 4) */
/* 目标400KHz, 33.1776MHz: MSSPEED = (33.1776M/400K/2 - 4)/2 ≈ 18.7, 取19 */
I2CCFG = 0x80; // ENI2C=1, 使能I2C功能
I2CCFG |= 0x40; // MSSL=1, 主机模式
I2CCFG |= 0x13; // MSSPEED=19 (0x13)
/* 设置I2C引脚: 使用默认引脚 P1.4(SDA), P1.5(SCL) */
/* I2C_S[1:0]=00, 已在P_SW2复位值中 */
P_SW2 &= ~0x30; // I2C_S[1:0]=00
}
/**************************** I2C起始信号 ************************************/
void I2C_Start(void)
{
unsigned char status;
I2CMSCR = 0x01; // MSCMD=0001, 发送START信号
while(!(I2CMSST & 0x40)); // 等待MSIF=1 (命令执行完成)
I2CMSST &= ~0x40; // 清除MSIF
}
/**************************** I2C停止信号 ************************************/
void I2C_Stop(void)
{
I2CMSCR = 0x06; // MSCMD=0110, 发送STOP信号
while(!(I2CMSST & 0x40)); // 等待MSIF=1
I2CMSST &= ~0x40; // 清除MSIF
}
/**************************** I2C发送一个字节 ********************************/
unsigned char I2C_SendByte(unsigned char dat)
{
I2CTXD = dat; // 写入要发送的数据
I2CMSCR = 0x02; // MSCMD=0010, 发送数据命令
while(!(I2CMSST & 0x40)); // 等待MSIF=1
I2CMSST &= ~0x40; // 清除MSIF
/* 接收ACK */
I2CMSCR = 0x03; // MSCMD=0011, 接收ACK命令
while(!(I2CMSST & 0x40)); // 等待MSIF=1
I2CMSST &= ~0x40; // 清除MSIF
return I2CMSST & 0x02; // 返回MSACKI (0=ACK, 1=NAK)
}
/**************************** OLED写命令 *************************************/
void OLED_WriteCmd(unsigned char cmd)
{
I2C_Start();
I2C_SendByte(OLED_ADDR); // 发送设备地址+写
I2C_SendByte(0x00); // 控制字节: 命令模式 (Co=0, D/C#=0)
I2C_SendByte(cmd); // 发送命令
I2C_Stop();
}
/**************************** OLED写数据 *************************************/
void OLED_WriteData(unsigned char dat)
{
I2C_Start();
I2C_SendByte(OLED_ADDR); // 发送设备地址+写
I2C_SendByte(0x40); // 控制字节: 数据模式 (Co=0, D/C#=1)
I2C_SendByte(dat); // 发送数据
I2C_Stop();
}
/**************************** OLED初始化 (SSD1306) ***************************/
void OLED_Init(void)
{
delay_ms(100); // 等待OLED上电稳定
OLED_WriteCmd(0xAE); // 关闭显示
OLED_WriteCmd(0xD5); // 设置显示时钟分频/振荡频率
OLED_WriteCmd(0x80); // 建议值
OLED_WriteCmd(0xA8); // 设置多路复用比
OLED_WriteCmd(0x3F); // 64行
OLED_WriteCmd(0xD3); // 设置显示偏移
OLED_WriteCmd(0x00); // 无偏移
OLED_WriteCmd(0x40); // 设置显示起始行
OLED_WriteCmd(0x8D); // 电荷泵设置
OLED_WriteCmd(0x14); // 使能电荷泵
OLED_WriteCmd(0x20); // 设置内存地址模式
OLED_WriteCmd(0x00); // 水平寻址模式
OLED_WriteCmd(0xA1); // 段重映射 (左右翻转)
OLED_WriteCmd(0xC8); // COM扫描方向 (上下翻转)
OLED_WriteCmd(0xDA); // COM引脚配置
OLED_WriteCmd(0x12); // 建议值
OLED_WriteCmd(0x81); // 对比度设置
OLED_WriteCmd(0xCF); // 对比度值
OLED_WriteCmd(0xD9); // 预充电周期
OLED_WriteCmd(0xF1); // 建议值
OLED_WriteCmd(0xDB); // VCOMH取消选择级别
OLED_WriteCmd(0x40); // 建议值
OLED_WriteCmd(0xA4); // 全局显示开启 (从RAM显示)
OLED_WriteCmd(0xA6); // 正常显示 (非反色)
OLED_WriteCmd(0xAF); // 开启显示
}
/**************************** OLED清屏 ***************************************/
void OLED_Clear(void)
{
unsigned char i, j;
for(i = 0; i < 8; i++) // 8页 (每页8行, 共64行)
{
OLED_WriteCmd(0xB0 + i); // 设置页地址
OLED_WriteCmd(0x00); // 设置列地址低4位
OLED_WriteCmd(0x10); // 设置列地址高4位
for(j = 0; j < 128; j++) // 128列
{
OLED_WriteData(0x00); // 写入0x00 (全灭)
}
}
}
/**************************** OLED显示字符串 *********************************/
/* 使用6x8字体,x: 列位置(0~127), y: 页位置(0~7) */
/* 注意: 此处为简化示例,仅显示固定字符串,实际项目中应包含字库 */
void OLED_ShowString(unsigned char x, unsigned char y, unsigned char *str)
{
/* 6x8 ASCII字库 (仅包含数字、字母和常用符号) */
static const unsigned char code font6x8[][6] = {
{0x00,0x00,0x00,0x00,0x00,0x00}, // 空格
{0x00,0x00,0x5F,0x00,0x00,0x00}, // !
{0x00,0x07,0x00,0x07,0x00,0x00}, // "
{0x14,0x7F,0x14,0x7F,0x14,0x00}, // #
{0x24,0x2A,0x7F,0x2A,0x12,0x00}, // $
{0x23,0x13,0x08,0x64,0x62,0x00}, // %
{0x36,0x49,0x55,0x22,0x50,0x00}, // &
{0x00,0x05,0x03,0x00,0x00,0x00}, // '
{0x00,0x1C,0x22,0x41,0x00,0x00}, // (
{0x00,0x41,0x22,0x1C,0x00,0x00}, // )
{0x08,0x2A,0x1C,0x2A,0x08,0x00}, // *
{0x08,0x08,0x3E,0x08,0x08,0x00}, // +
{0x00,0x50,0x30,0x00,0x00,0x00}, // ,
{0x08,0x08,0x08,0x08,0x08,0x00}, // -
{0x00,0x60,0x60,0x00,0x00,0x00}, // .
{0x20,0x10,0x08,0x04,0x02,0x00}, // /
{0x3E,0x51,0x49,0x45,0x3E,0x00}, // 0
{0x00,0x42,0x7F,0x40,0x00,0x00}, // 1
{0x42,0x61,0x51,0x49,0x46,0x00}, // 2
{0x21,0x41,0x45,0x4B,0x31,0x00}, // 3
{0x18,0x14,0x12,0x7F,0x10,0x00}, // 4
{0x27,0x45,0x45,0x45,0x39,0x00}, // 5
{0x3C,0x4A,0x49,0x49,0x30,0x00}, // 6
{0x01,0x71,0x09,0x05,0x03,0x00}, // 7
{0x36,0x49,0x49,0x49,0x36,0x00}, // 8
{0x06,0x49,0x49,0x29,0x1E,0x00}, // 9
{0x00,0x36,0x36,0x00,0x00,0x00}, // :
{0x00,0x56,0x36,0x00,0x00,0x00}, // ;
{0x00,0x08,0x14,0x22,0x41,0x00}, // <
{0x14,0x14,0x14,0x14,0x14,0x00}, // =
{0x41,0x22,0x14,0x08,0x00,0x00}, // >
{0x02,0x01,0x51,0x09,0x06,0x00}, // ?
{0x32,0x49,0x79,0x41,0x3E,0x00}, // @
{0x7E,0x11,0x11,0x11,0x7E,0x00}, // A
{0x7F,0x49,0x49,0x49,0x36,0x00}, // B
{0x3E,0x41,0x41,0x41,0x22,0x00}, // C
{0x7F,0x41,0x41,0x22,0x1C,0x00}, // D
{0x7F,0x49,0x49,0x49,0x41,0x00}, // E
{0x7F,0x09,0x09,0x01,0x01,0x00}, // F
{0x3E,0x41,0x41,0x51,0x32,0x00}, // G
{0x7F,0x08,0x08,0x08,0x7F,0x00}, // H
{0x00,0x41,0x7F,0x41,0x00,0x00}, // I
{0x20,0x40,0x41,0x3F,0x01,0x00}, // J
{0x7F,0x08,0x14,0x22,0x41,0x00}, // K
{0x7F,0x40,0x40,0x40,0x40,0x00}, // L
{0x7F,0x02,0x04,0x02,0x7F,0x00}, // M
{0x7F,0x04,0x08,0x10,0x7F,0x00}, // N
{0x3E,0x41,0x41,0x41,0x3E,0x00}, // O
{0x7F,0x09,0x09,0x09,0x06,0x00}, // P
{0x3E,0x41,0x51,0x21,0x5E,0x00}, // Q
{0x7F,0x09,0x19,0x29,0x46,0x00}, // R
{0x46,0x49,0x49,0x49,0x31,0x00}, // S
{0x01,0x01,0x7F,0x01,0x01,0x00}, // T
{0x3F,0x40,0x40,0x40,0x3F,0x00}, // U
{0x1F,0x20,0x40,0x20,0x1F,0x00}, // V
{0x7F,0x20,0x18,0x20,0x7F,0x00}, // W
{0x63,0x14,0x08,0x14,0x63,0x00}, // X
{0x03,0x04,0x78,0x04,0x03,0x00}, // Y
{0x61,0x51,0x49,0x45,0x43,0x00}, // Z
};
unsigned char i;
unsigned char ch;
while(*str != '\0')
{
ch = *str - 0x20; // 字库从空格(0x20)开始
OLED_WriteCmd(0xB0 + y); // 设置页地址
OLED_WriteCmd(0x00 + (x & 0x0F)); // 列低4位
OLED_WriteCmd(0x10 + ((x >> 4) & 0x0F)); // 列高4位
for(i = 0; i < 6; i++)
{
if(ch < sizeof(font6x8) / 6)
{
OLED_WriteData(font6x8[ch][i]);
}
else
{
OLED_WriteData(0x00);
}
}
x += 6; // 每个字符占6列
str++;
if(x > 122) // 超出屏幕宽度则换行
{
x = 0;
y++;
if(y > 7) y = 0;
}
}
}
/**************************** OLED显示数字 ***********************************/
void OLED_ShowNum(unsigned char x, unsigned char y, unsigned int num, unsigned char len)
{
unsigned char buf[6];
unsigned char i;
buf[len] = '\0';
for(i = len; i > 0; i--)
{
buf[i - 1] = '0' + (num % 10);
num /= 10;
}
OLED_ShowString(x, y, buf);
}
/**************************** 显示更新函数 ***********************************/
void Display_Update(void)
{
unsigned char buf[20];
unsigned int display_val;
unsigned char unit_str[] = "kPa";
if(g_mode == 0)
{
/* 实时模式: 显示当前压力值 */
OLED_ShowString(0, 2, "Real: kPa");
/* 显示压力值 (整数部分) */
display_val = g_pressure_value / 10;
OLED_ShowNum(30, 2, display_val, 4);
/* 显示小数点后一位 */
OLED_ShowString(66, 2, ".");
display_val = g_pressure_value % 10;
OLED_ShowNum(72, 2, display_val, 1);
}
else
{
/* 峰值模式: 显示峰值压力 */
OLED_ShowString(0, 2, "Peak: kPa");
display_val = g_peak_pressure / 10;
OLED_ShowNum(30, 2, display_val, 4);
OLED_ShowString(66, 2, ".");
display_val = g_peak_pressure % 10;
OLED_ShowNum(72, 2, display_val, 1);
}
/* 显示VCC电压 (第4行) */
OLED_ShowString(0, 4, "VCC:");
OLED_ShowNum(28, 4, g_vcc_mv, 4);
OLED_ShowString(56, 4, "mV");
/* 显示超压报警状态 (第5行) */
if(g_over_pressure_flag)
{
OLED_ShowString(0, 6, "! OVER PRESSURE !");
}
else
{
OLED_ShowString(0, 6, " "); // 清除报警行
}
}
/**************************** 串口1初始化 ************************************/
void UART1_Init(void)
{
/* 串口1: 使用定时器2作为波特率发生器 */
/* 波特率 = 115200bps */
/* 系统时钟 33.1776MHz */
/* 定时器2重载值 = 65536 - SYSclk / 4 / 波特率 (1T模式) */
/* = 65536 - 33177600 / 4 / 115200 */
/* = 65536 - 72 = 65464 = 0xFFB8 */
S1BRT = 1; // 串口1选择定时器2为波特率发生器
AUXR &= ~0x10; // T2x12=0 (12T模式)
/* 12T模式: 重载值 = 65536 - SYSclk / 12 / 4 / 波特率 */
/* = 65536 - 33177600 / 12 / 4 / 115200 */
/* = 65536 - 6 = 65530 = 0xFFFA */
T2L = 0xFA;
T2H = 0xFF;
AUXR |= 0x10; // T2R=1, 启动定时器2
SCON = 0x50; // 模式1 (8位UART), REN=1 允许接收
PCON &= 0x7F; // SMOD=0 (波特率不倍频)
ES = 1; // 使能串口1中断
}
/**************************** 串口1发送字符串 ********************************/
void UART1_SendString(unsigned char *str)
{
while(*str != '\0')
{
SBUF = *str; // 写入发送缓冲区
while(!TI); // 等待发送完成
TI = 0; // 清除发送中断标志
str++;
}
}
/**************************** 毫秒延时函数 ***********************************/
void delay_ms(unsigned int ms)
{
unsigned int i, j;
for(i = 0; i < ms; i++)
{
/* 33.1776MHz下,约1ms */
for(j = 0; j < 3317; j++)
{
_nop_();
}
}
}
/**************************** 微秒延时函数 ***********************************/
void delay_us(unsigned int us)
{
unsigned int i;
for(i = 0; i < us; i++)
{
_nop_();
_nop_();
_nop_();
}
}
程序说明
一、系统功能概述
本程序实现了一个完整的数显压力表,主要功能模块如下:
| 模块 |
功能 |
使用的外设 |
| 压力采集 |
读取压力传感器模拟电压,转换为压力值 |
ADC通道0 (P1.0) |
| 电压自校准 |
利用内部1.19V参考源反推VCC,消除电源波动影响 |
ADC通道15 |
| 显示输出 |
OLED实时显示压力值、峰值、VCC电压 |
I2C总线 (P1.4/P1.5) |
| 串口调试 |
通过串口输出压力数据到PC |
UART1 (P3.0/P3.1) |
| 按键交互 |
切换实时/峰值模式,长按清零峰值 |
GPIO (P3.2) |
| 报警指示 |
超压时点亮LED |
GPIO (P3.3) |
二、关键设计要点
1. ADC电压自校准技术
根据手册"利用ADC第15通道(内部1.19V参考信号源-BGV)测量外部电压"章节,程序通过两次ADC采样实现高精度测量:
- 先采样内部1.19V参考源(通道15),得到
adc_bg
- 再采样压力传感器(通道0),得到
adc_ch0
- 反推VCC:
VCC = 1.19V × 4096 / adc_bg
- 计算输入电压:
Vin = VCC × adc_ch0 / 4096
- 转换为压力值:
Pressure = Vin / 满量程电压 × 满量程压力
这种方法的好处是不受VCC波动影响,即使电池供电电压下降,测量精度依然保持。
2. ADC时序配置
根据手册要求:
- ADC时钟频率不超过800KHz(12位模式)
- SMPDUTY不能小于10,建议设为15以上
- 程序设置
ADCTIM = 0x3F,采样时间设为最长31个ADC时钟,保证精度
3. I2C总线配置
根据手册"I^2C总线速度=SYSCLK / 2 / (MSSPEED × 2 + 4)"公式:
- 目标400KHz,33.1776MHz下
MSSPEED = 19
- I2C引脚使用开漏模式,需外接4.7kΩ上拉电阻
4. 定时器2配置
使用定时器2的16位自动重装载模式,产生1ms系统滴答,用于显示刷新和串口输出的定时控制。
三、硬件接线表
| STC32G8K64引脚 |
连接对象 |
说明 |
| P1.0 |
压力传感器输出 |
ADC输入,03.3V对应0满量程 |
| P1.4 |
OLED SDA |
I2C数据线,需4.7kΩ上拉至VCC |
| P1.5 |
OLED SCL |
I2C时钟线,需4.7kΩ上拉至VCC |
| P3.0 |
串口调试RxD |
接USB转串口模块的TxD |
| P3.1 |
串口调试TxD |
接USB转串口模块的RxD |
| P3.2 |
按键 |
按下为低电平,内部弱上拉 |
| P3.3 |
报警LED |
推挽输出,高电平点亮(串330Ω电阻) |
| VCC |
3.3V电源 |
注意:ADC_VRef+必须接VCC或外部参考电压 |
| GND |
公共地 |
|
四、使用步骤
- 打开STC-ISP下载软件,选择芯片型号
STC32G8K64
- 设置内部IRC时钟为
33.1776MHz
- 编译下载本程序到芯片
- 连接硬件:压力传感器输出接P1.0,OLED接P1.4/P1.5
- 上电运行,OLED将显示实时压力值
- 按键操作:
- 短按P3.2:切换"实时值/峰值"显示模式
- 长按P3.2(>1秒):峰值清零并返回实时模式
五、用户可调整参数
程序开头的宏定义可根据实际传感器参数修改:
#define PRESSURE_MAX 1000 // 满量程压力值 (kPa)
#define PRESSURE_FULL_SCALE_V 3300 // 满量程对应电压 (mV)
#define OVER_PRESSURE_THRESHOLD 900 // 超压报警阈值 (kPa)
例如,若传感器为01.6MPa对应05V输出,则改为:
#define PRESSURE_MAX 1600 // 1600 kPa
#define PRESSURE_FULL_SCALE_V 5000 // 5000 mV
注意:如果传感器输出超过3.3V,需要使用电阻分压后接入P1.0,或使用外部ADC参考电压。STC32G8K64的ADC_VRef+引脚必须连接有效参考电压(接VCC或外部精密参考源),不可浮空。
🔍 搜索 "数显压力表 STC32G8K64 程序示例 程序注解"
📌 相关论坛讨论
- 【AI助手】@AiCube-ISP-V6.96Z中,【自动生成程序】+【查错高手】
⏱️ 查找用时 18.5s | 总回答用时 90.7s