蓝桥杯CT107D单片机实战:用PCF8591同时读取光敏和可调电阻电压(附完整源码)
·
蓝桥杯CT107D单片机进阶实战:双通道电压采样与动态显示全解析
在蓝桥杯单片机竞赛的备战过程中,掌握多通道模拟信号采集技术是突破硬件编程瓶颈的关键一环。CT107D开发板搭载的PCF8591芯片为参赛选手提供了便捷的A/D转换解决方案,而如何同时采集光敏电阻和可调电阻的电压值并实现动态显示,则是检验I2C通信与数据处理能力的经典案例。本文将手把手带你从零搭建完整工程,深入解析每个技术细节。
1. 硬件架构与原理剖析
1.1 PCF8591芯片工作机理
PCF8591作为一款集成了4路模拟输入、1路模拟输出的8位A/D-D/A转换器,其核心特性包括:
- 4路模拟输入:支持单端或差分输入模式,本文案例使用AIN1和AIN3两个独立通道
- 8位分辨率:对应0-255的数字量输出范围,量化精度约为19.5mV(5V基准时)
- I2C接口:标准400kHz快速模式兼容,设备地址固定为0x90(写)/0x91(读)
实际应用中需注意:PCF8591的转换速度约11ms/次,连续采样时需要适当加入延时
1.2 电路连接关键点
CT107D开发板的硬件连接需要特别注意以下接口:
| 元件 | 连接位置 | 参数特性 |
|---|---|---|
| 光敏电阻RD1 | PCF8591 AIN1 | 光照越强阻值越小(2-20kΩ) |
| 可调电阻RB2 | PCF8591 AIN3 | 10kΩ线性可调 |
| 基准电压 | PCF8591 VREF | 默认接开发板5V电源 |
// 硬件初始化验证代码
void Hardware_Check() {
PCF8591_Init(); // I2C初始化
LED_Init(); // 调试指示灯初始化
SEG_Init(); // 数码管初始化
}
2. 工程搭建与驱动移植
2.1 I2C驱动文件移植
蓝桥杯官方提供的I2C驱动通常包含以下关键函数:
// I2C基础操作函数
void IIC_Start(void);
void IIC_Stop(void);
void IIC_SendByte(unsigned char dat);
unsigned char IIC_RecByte(void);
void IIC_SendAck(bit ackbit);
bit IIC_WaitAck(void);
移植步骤:
- 将
iic.h和iic.c添加到工程目录 - 检查头文件包含路径是否正确
- 验证延时函数与硬件匹配(STC15系列需要调整延时)
2.2 工程结构规划
推荐采用模块化编程结构:
Project/
├── Main.c // 主循环与调度
├── PCF8591.c // A/D转换处理
├── Display.c // 数码管显示
├── iic.c // I2C底层驱动
└── Includes.h // 全局定义与声明
3. 核心代码实现解析
3.1 多通道采样逻辑
PCF8591的通道切换需要遵循特定时序:
- 发送控制字节选择通道
- 启动转换后等待足够时间
- 读取转换结果
unsigned char Read_PCF8591(unsigned char channel) {
unsigned char result;
IIC_Start();
IIC_SendByte(0x90); // 设备地址+写模式
IIC_WaitAck();
IIC_SendByte(0x40 | channel); // 启用A/D转换+通道选择
IIC_WaitAck();
IIC_Stop();
Delay_ms(15); // 等待转换完成
IIC_Start();
IIC_SendByte(0x91); // 设备地址+读模式
IIC_WaitAck();
result = IIC_RecByte(); // 读取转换结果
IIC_SendAck(1); // 非应答终止传输
IIC_Stop();
return result;
}
3.2 电压换算算法
8位ADC的电压换算公式:
实际电压 = 采样值 × (基准电压 / 255)
考虑到数码管显示需要保留两位小数,可采用整数运算优化:
void Process_Voltage(void) {
// 读取原始采样值
adc1_raw = Read_PCF8591(1);
adc3_raw = Read_PCF8591(3);
// 电压换算(放大100倍避免浮点运算)
adc1_volt = adc1_raw * 500 / 255; // 等效于 (adc1_raw*5.0/255)*100
adc3_volt = adc3_raw * 500 / 255;
// 分离各位数字
disp_buf[0] = adc1_volt / 100; // 十位
disp_buf[1] = (adc1_volt / 10) % 10; // 个位
disp_buf[2] = adc1_volt % 10; // 十分位
disp_buf[3] = adc3_volt / 100;
disp_buf[4] = (adc3_volt / 10) % 10;
disp_buf[5] = adc3_volt % 10;
}
4. 动态显示优化技巧
4.1 数码管刷新策略
采用定时中断实现稳定刷新:
// 定时器0初始化(1ms中断)
void Timer0_Init() {
AUXR |= 0x80; // 1T模式
TMOD &= 0xF0;
TL0 = 0xCD;
TH0 = 0xD4;
TR0 = 1;
ET0 = 1;
EA = 1;
}
// 中断服务程序
void Timer0_ISR() interrupt 1 {
static unsigned char pos = 0;
SEG_OFF_ALL(); // 关闭所有位选
SEG_Set(pos, disp_buf[pos]); // 显示当前位
pos = (pos + 1) % 6; // 循环6位数码管
}
4.2 显示效果优化
针对不同环境光照条件,可增加自动亮度调节:
void Auto_Brightness() {
unsigned char light = Read_PCF8591(1);
if(light > 200) SEG_Brightness(3); // 强光环境高亮度
else if(light > 100) SEG_Brightness(2);
else SEG_Brightness(1); // 弱光环境低亮度
}
5. 调试常见问题解决方案
5.1 采样值不稳定
可能原因及对策:
- 电源噪声:在VREF引脚增加0.1μF去耦电容
- 信号干扰:缩短传感器引线长度,必要时加屏蔽
- I2C时序问题:用逻辑分析仪捕获波形验证时序参数
5.2 数码管显示异常
典型故障排查流程:
观察现象 → 检查位选信号 → 验证段码数据 → 测量驱动电压 → 排查程序逻辑
关键测试点:
- 锁存器使能信号(LE)是否正常
- 段码数据与预期编码表是否一致
- 动态刷新频率是否在100-500Hz范围内
6. 进阶功能扩展
6.1 数据滤波处理
采用滑动平均滤波提升稳定性:
#define FILTER_LEN 8
unsigned char filter_buf[FILTER_LEN];
unsigned char filter_index = 0;
unsigned char Moving_Average(unsigned char new_val) {
static unsigned int sum = 0;
sum -= filter_buf[filter_index];
sum += new_val;
filter_buf[filter_index] = new_val;
filter_index = (filter_index + 1) % FILTER_LEN;
return (unsigned char)(sum / FILTER_LEN);
}
6.2 阈值报警功能
增加光强阈值检测:
void Check_Light_Threshold() {
if(adc1_volt < light_threshold) {
BUZZER_ON();
LED_Alert_Blink();
} else {
BUZZER_OFF();
LED_Normal();
}
}
在项目开发过程中,我发现合理使用状态机架构能显著提升代码可维护性。将采样、处理、显示等任务分配到不同定时周期执行,既保证了实时性又避免了函数阻塞。例如设置10ms采样周期、5ms显示刷新周期,通过标志位触发各任务执行。
更多推荐



所有评论(0)