1. 项目概述
PIC16F1824和PIC12F1822这两款8位微控制器在嵌入式开发领域有着广泛的应用,特别是它们的模拟数字转换(ADC)功能,让开发者能够轻松处理各种传感器信号。作为Microchip公司PIC单片机家族中的中端产品,它们以合理的价格提供了不错的性能表现。
我在工业自动化项目中多次使用过这两款芯片,发现它们的ADC模块虽然不如高端芯片强大,但对于大多数常规应用已经足够。特别是PIC16F1824,拥有10位分辨率、多达12个ADC输入通道,采样速率最高可达100ksps,完全能满足温度监测、光照检测等常见场景的需求。
2. 硬件准备与电路设计
2.1 芯片选型对比
PIC16F1824和PIC12F1822的主要区别在于封装和资源:
- PIC16F1824采用14/20引脚封装,提供12个ADC输入通道
- PIC12F1822则是8引脚封装,只有4个ADC通道
- 两者ADC核心参数相同:10位分辨率,相同转换原理
在实际项目中,如果需要监测多个模拟信号,PIC16F1824显然是更好的选择。而对于简单的单路信号采集,小巧的PIC12F1822就能胜任。
2.2 参考电路设计要点
设计ADC电路时,有几个关键点需要注意:
-
参考电压配置:
- 可以使用芯片内部的4.096V参考电压
- 对于精度要求高的应用,建议外接精密参考源
- 参考电压值直接影响转换结果的精度
-
输入信号调理:
- 输入信号幅度不应超过VREF
- 建议在ADC输入前加入RC低通滤波(如1kΩ+0.1μF)
- 高阻抗信号源应考虑使用电压跟随器
-
电源去耦:
- 每个电源引脚都应放置0.1μF陶瓷电容
- 模拟电源最好与数字电源分开,使用磁珠隔离
重要提示:PIC单片机的ADC输入阻抗不高(约10kΩ),直接连接高阻抗传感器会导致测量误差。这种情况下必须使用运放缓冲。
3. 软件配置与编程实现
3.1 ADC模块初始化
在MPLAB X IDE中使用XC8编译器,ADC初始化代码如下:
c复制void ADC_Init(void) {
// 1. 配置ADC时钟源
ADCON0bits.ADCS = 0b01; // Fosc/8
// 2. 选择结果格式为右对齐
ADFM = 1;
// 3. 选择参考电压源
ADCON1bits.ADPREF = 0b00; // VDD作为正参考
ADCON1bits.ADNREF = 0; // VSS作为负参考
// 4. 选择ADC通道
ADCON0bits.CHS = 0b0000; // 初始选择AN0
// 5. 开启ADC模块
ADON = 1;
// 6. 等待采集电容充电
__delay_us(20);
}
3.2 单次转换实现
单次ADC转换的典型流程:
- 选择输入通道
- 启动转换
- 等待转换完成
- 读取结果
对应的代码实现:
c复制unsigned int ADC_Read(unsigned char channel) {
// 1. 选择通道(确保不超出范围)
if(channel > 11) channel = 0;
ADCON0bits.CHS = channel;
// 2. 等待通道切换稳定
__delay_us(5);
// 3. 启动转换
GO_nDONE = 1;
// 4. 等待转换完成
while(GO_nDONE);
// 5. 返回10位结果
return ((ADRESH << 8) | ADRESL);
}
3.3 连续采样与滤波处理
在实际应用中,单次采样往往不够可靠。我通常采用以下两种方法来提高稳定性:
- 多次采样取平均:
c复制#define SAMPLE_TIMES 16
unsigned int ADC_Read_Average(unsigned char channel) {
unsigned long sum = 0;
for(int i=0; i<SAMPLE_TIMES; i++) {
sum += ADC_Read(channel);
__delay_us(100); // 采样间隔
}
return (unsigned int)(sum / SAMPLE_TIMES);
}
- 滑动窗口滤波:
c复制#define WINDOW_SIZE 8
unsigned int window[WINDOW_SIZE] = {0};
unsigned char index = 0;
unsigned int ADC_Read_Filtered(unsigned char channel) {
// 移出最旧数据
for(int i=1; i<WINDOW_SIZE; i++) {
window[i-1] = window[i];
}
// 存入新数据
window[WINDOW_SIZE-1] = ADC_Read(channel);
// 计算平均值
unsigned long sum = 0;
for(int i=0; i<WINDOW_SIZE; i++) {
sum += window[i];
}
return (unsigned int)(sum / WINDOW_SIZE);
}
4. 实际应用案例分析
4.1 温度监测系统
以常见的LM35温度传感器为例,连接和读取方法如下:
-
硬件连接:
- LM35输出接PIC的AN0引脚
- 电源接3.3V或5V(根据PIC工作电压)
- 输出端对地接0.1μF电容
-
温度计算:
- LM35输出10mV/°C
- 假设使用5V参考电压,ADC分辨率=5V/1024≈4.88mV
- 温度值 = (ADC结果 × 4.88) / 10
代码实现:
c复制float Read_Temperature(void) {
unsigned int adcValue = ADC_Read_Average(0);
float voltage = adcValue * 4.88 / 1000.0; // 转换为电压(V)
return voltage * 100.0; // LM35: 10mV/°C
}
4.2 光照强度检测
使用光敏电阻的典型电路:
-
分压电路设计:
- 光敏电阻与固定电阻(如10kΩ)组成分压
- 中点接ADC输入
- 光照越强,电阻越小,电压越高
-
代码处理:
- 直接读取ADC值即可反映光照强度
- 可设置阈值实现光控开关
c复制#define LIGHT_THRESHOLD 500
void Check_Light(void) {
unsigned int light = ADC_Read(1);
if(light > LIGHT_THRESHOLD) {
LED = ON;
} else {
LED = OFF;
}
}
5. 性能优化与问题排查
5.1 提高ADC精度的技巧
-
参考电压选择:
- 使用外部精密基准源(如TL431)
- 避免使用电源电压作为参考
-
采样时间调整:
- 信号源阻抗高时增加采样时间
- 通过ADCON2寄存器配置
-
软件校准:
- 在已知输入下测量误差
- 存储校准系数到EEPROM
c复制// 两点校准示例
float adc_gain = 1.02; // 通过校准获得
float adc_offset = 3.5;
unsigned int ADC_Read_Calibrated(unsigned char channel) {
unsigned int raw = ADC_Read_Average(channel);
return (unsigned int)(raw * adc_gain + adc_offset);
}
5.2 常见问题与解决方法
-
读数不稳定:
- 检查电源是否干净(示波器观察)
- 增加采样电容(输入端对地接0.1-1μF)
- 实施软件滤波
-
结果偏差大:
- 确认参考电压准确
- 检查输入信号是否超出范围
- 验证分压电阻精度
-
转换速度慢:
- 选择更快的时钟源(ADCON0bits.ADCS)
- 减少采样时间(如果信号允许)
- 考虑使用中断方式
调试技巧:在调试ADC问题时,我习惯先用已知电压(如电源分压)测试,确认基本功能正常后再接实际传感器。这样可以快速定位是ADC配置问题还是传感器电路问题。
6. 高级应用:使用DMA加速ADC采样
对于PIC16F1824,虽然没有真正的DMA控制器,但可以通过巧妙的中断使用实现类似效果:
- 配置定时器触发ADC转换
- 在ADC中断中存储结果到数组
- 主程序处理完整数据集
c复制#define SAMPLE_COUNT 64
unsigned int adcBuffer[SAMPLE_COUNT];
unsigned char sampleIndex = 0;
void interrupt ISR(void) {
if(ADIF) {
ADIF = 0; // 清除中断标志
adcBuffer[sampleIndex++] = (ADRESH << 8) | ADRESL;
if(sampleIndex >= SAMPLE_COUNT) {
sampleIndex = 0;
// 设置数据处理标志
}
GO_nDONE = 1; // 启动下一次转换
}
}
void ADC_DMA_Init(void) {
// 配置定时器2每1ms触发一次
T2CON = 0b00000010; // 预分频1:1,后分频1:1
PR2 = 249; // 对于16MHz Fosc: (249+1)*4/16MHz = 62.5us
TMR2IE = 1;
TMR2ON = 1;
// 配置ADC
ADCON0bits.ADCS = 0b10; // 使用Timer2触发
// 其他ADC配置...
// 全局中断使能
PEIE = 1;
GIE = 1;
}
这种技术可以实现不占用CPU时间的连续采样,特别适合需要同时处理其他任务的应用。